Android Debugging by Tutorials
Android Debugging by Tutorials
Android device: You’ll need a device to test out the book examples. You can
either use an emulator (AVD) or a real device.
Kotlin 1.6 or later: This book uses Kotlin 1.6 throughout. The materials may
work with older Kotlin versions, but they aren’t tested.
Android Studio 2021.2.1 or later: As Android Studio is the main tool for
Android app development, you’ll need to install AS Chipmunk or a newer
version on your machine to try out the book examples. Download the latest
version from the Android developer site:
https://developer.android.com/studio.
The code covered in this book depends on Android 12, Kotlin 1.6 and Android
Studio 2021.2.1 — you may experience issues if you try to work with older
versions.
2
Android Debugging by Tutorials
https://github.com/raywenderlich/adf-materials/tree/editions/1.0
Forums
We’ve also set up an official forum for the book at
https://forums.raywenderlich.com/c/books/android-debugging-by-tutorials.
This is a great place to ask questions about the book or to submit any errors you
may find.
3
Android Debugging by Tutorials
iii Dedications
— Vincenzo Guzzi
“To my wife, Kari. Thank you for always believing in me and helping
me realize all that I could do when I set my mind to it. And to my
children, Logan and Lana. Thank you for revitalizing that child-like
wonder that has kept me passionate about learning and trying new
things.”
— Zac Lippard
4
Android Debugging by Tutorials
5
Android Debugging by Tutorials About the Team
v Introduction
Welcome to the First Edition of Android Debugging by Tutorials, created for Kotlin
1.6 and Android Studio 2021.2.1!
Android Debugging focuses on explaining how to debug your app and avoid
unexpected app behavior. Once you read this book, you’ll be able to find every
bug or issue inside your app, fix it and move your app to a higher level. You
won’t doubt whether your end users will experience weird results or bad app
performance anymore. You’ll be aware of all issues reproduced at least once
and know which steps to take to resolve them.
The most important part is getting familiar with Android Studio’s debugging
tools. Once you tackle each of them and find out what they offer, you won’t ever
stop using them!
It’s easy to notice and resolve bugs that are visible on the UI or can be
experienced while using the app. The problem arises when you have hidden
bugs that can happen occasionally or when your app structure or architecture
leads to memory or CPU management issues.
Thankfully, Android Studio contains built-in profilers that can present these
issues to you in a specific way. You only need to learn how to use and interpret
these profilers. Lucky you! This book will teach you everything from scratch.
At the moment of publishing this book, there were no other guiding books
based on the Android debugging topic. This is an excellent opportunity for you
to learn something new from the debugging field and extend your skills while
having everything related to debugging apps in one place.
You’ll start with learning debugging basics and simple mechanisms which you
can use to find and investigate bugs. After you acknowledge the basics, you’ll
move to more complex tools. Don’t worry, you don’t need to install any other
software for this book. Everything you need is already prepared for you in
Android Studio!
With a lot of practice, this book will make you a debugging expert! But don’t stop
here! The book contains some great references which you can use to continue
your debug journey.
7
Android Debugging by Tutorials Introduction
The book doesn’t contain a lot of programming theory. It’s more focused on
explaining tool mechanisms, their options, actions and results. Understanding
these tools is the first step in building more performant apps.
If you want to notice even the smallest detail, read these chapters in order.
Chapters are created and sorted so Android beginners can easily follow them.
However, if you’re an advanced developer, there’s a possibility you’d like to skip
some chapters. In case that happens, be sure to continue with the starter
project attached to the chapter you’re skipping to. Every starter project
corresponds to the app state as it was at the end of the previous chapter. So, to
have everything functional for the next step, remember to switch to the correct
app package.
There is only one sample app used in the book called Podplay, but it’s full of
bugs that you’ll try to fix while going through this book. That means don’t
expect a fully functional and top-quality app at the beginning. This app should
provide enough examples to practice inspecting and resolving issues. In case
the app looks familiar to you, there is a possibility you have already used it while
reading our book Android Apprentice.
Currently, the most popular language for building Android apps is Kotlin. It’s
also Google’s preferred language for Android because of its simplicity.
Therefore, this book and sample app use Kotlin code to make you up to date
with modern technologies. For running the sample app, you need to use
Android Studio.
Most of this book’s steps focus on manipulating the Android Studio built-in
tools and their actions. There are a few places where you’ll need to modify the
code inside the sample app. You can either type the code in Android Studio
immediately, or you can tackle the issue on your own and get back to the book
to reveal the solution.
To make the book more interesting and to give you a chance to test what you’ve
learned, we prepared several challenges that you can find near the chapter’s
end.
Every chapter contains one or more references that you can follow to learn
more about related topics.
8
Android Debugging by Tutorials Introduction
This book is split into two main sections:
Chapter 1: Getting Started — In the first chapter, you’ll get familiar with the
sample app used inside the book. You’ll find out how to use the IDE tools and
prepare your debugging environment for usage. This chapter will teach you
the basics of Android Debug Bridge and how to attach a debugger to a
running app.
9
Android Debugging by Tutorials Introduction
Inspector — The last chapter in this section will teach you how to handle
debugging operations running on background threads. You’ll use the
Background Task Inspector tool, which gives you an option to view and
optimize background tasks.
Chapter 9: Profile CPU Activity — The opening chapter will introduce you to
the CPU Profiler. This huge and very capable tool contains several timelines
for tracking CPU activity and records detailed information in the form of
system, method and function traces.
Chapter 10: Profile Memory Usage — In this chapter, you’ll understand how
to use Memory Profiler to inspect memory allocations and investigate where
memory leaks are in your app. You’ll also focus on capturing heap dumps.
Chapter 12: Android Energy Profiler — The last chapter in this book will
present how to use Energy Profiler to resolve issues with long-running
services in your app. After that, you’ll read about wake locks, jobs and
alarms. With this knowledge, you’ll be able to reduce draining the battery and
provide a good user experience for the end user.
10
Android Debugging by Tutorials
1 Getting Started
Written by Zac Lippard
Imagine you finish an important feature for your Android application and you
attempt to run it on your device. The app seems to launch, hooray! But, to your
dismay the app crashes not a moment later! What do you do now?
In this chapter, you’ll set up your debugging environment using the built-in
tools Android Studio provides. You’ll also learn about some of the basics of the
Android Debug Bridge (ADB) that powers these debugging tools. By the end of
the chapter, you’ll be able to connect the debugger to the sample app running
on either a device or emulator.
Throughout this book, you’ll use the PodPlay sample app to learn about the
debugging tools available to you.
Debugging
So, what exactly is debugging anyway? Well, debugging is the process of finding
and resolving bugs. A bug can be any defect in the software that causes
unexpected errors and crashes.
Fun fact, while the engineering term “bug” was used as early as the 1870s by
Thomas Edison, it became a more popular term in the 20th century. In the late
1940s, Admiral Grace Hopper had a team working on the Mark II computer
when they noticed a moth trapped in one of the relays. The team had to literally
“debug” the system to fix the error!
A debugger is a tool that helps software engineers track and monitor a program
and change values in memory. Android Studio offers a built-in debugger tool
that can help accomplish these tasks. The debugger allows you to add
breakpoints to suspend execution of the Android application, monitor memory
and CPU usage and provides a whole bunch of other tracking tools. You’ll learn
11
Android Debugging by Tutorials Chapter 1: Getting Started
more about breakpoints in Chapter 2, “Navigating Your Code With Breakpoints”.
Next, you’ll learn how to connect the Android Studio debugger to an Android
application.
Similar to running the application, Android Studio will build the app if
necessary. Then, it will install and run the app on the connected device. The
additional step after the app starts is that the debugger is immediately attached
to the running application.
If you see a Waiting For Debugger alert dialog on the screen, you’ve
successfully connected the debugger!
After the debugger is attached, you’ll see the main app screen stating that you
haven’t subscribed to any podcasts yet.
12
Android Debugging by Tutorials Chapter 1: Getting Started
Once the application is running in debug mode, you can stop the application by
clicking on the Stop icon in the AS toolbar.
A popup will appear, and you can select the running app to connect to.
13
Android Debugging by Tutorials Chapter 1: Getting Started
Debugging Wirelessly
Android Studio also allows connecting and debugging physical devices over
WiFi. The main benefit of wireless debugging is the ability to avoid USB
connectivity problems, such as driver installation or accidentally disconnecting
your device if you have a damaged cable.
To start, ensure that your workstation and your device are on the same
network. The device you’re using will need to have Developer options enabled.
To enable Developer options go to the Settings app and choose About phone.
Scroll to the bottom where the Build number option is; if you don’t see it, look
for Software information. Tap the option seven times. Congrats! You now have
access to the Developer options on the device.
14
Android Debugging by Tutorials Chapter 1: Getting Started
Once you enable the Developer options, go and find that menu, typically under
Settings ▸ Developer options or Settings ▸ System ▸ Developer Options, and
look for the Debugging section.
Turn on the Wireless debugging option. You’ll be prompted with the following
dialog to accept wireless debugging on the device’s currently connected
network:
15
Android Debugging by Tutorials Chapter 1: Getting Started
Once your device has enabled wireless debugging, you’re ready to connect it to
Android Studio! To do so, click the devices dropdown in the toolbar in Android
Studio and select the Pair Devices Using Wi-Fi option.
For Android 11+ devices, you can scan the QR code to pair. Open the Wireless
Debugging option and choose Pair device with QR code. Scan the QR code
from the Android Studio dialog, and it should automatically pair.
You can pair the device with a pairing code for devices lower than Android 11.
To do this, open the Pair using pairing code tab in the dialog. On your device,
tap Wireless Debugging and choose the Pair device with pairing code option.
A dialog on the device will appear with a code, and the device will appear on the
Android Studio pairing devices dialog. Click Pair to be able to enter the code.
16
Android Debugging by Tutorials Chapter 1: Getting Started
The device should pair, and you’ll now be able to run or debug your app
wirelessly!
17
Android Debugging by Tutorials Chapter 1: Getting Started
1. Select the + icon at the top-right of the dialog to add a new configuration.
Note: If you plan on debugging more than two devices at once, you’ll need to
add additional configurations for each additional emulator/device that you’ve
connected.
18
Android Debugging by Tutorials Chapter 1: Getting Started
Next, while the app configuration is still debugging and paused on the
breakpoint, switch to the app 2 configuration in the toolbar dropdown. Select
your second device, and then click the Debug icon.
Now, the breakpoint will hit again, but notice that this time in the debug window,
there is now an app 2 tab that is active.
19
Android Debugging by Tutorials Chapter 1: Getting Started
This represents the configuration and device pair that you ran the second time.
You can toggle between the tabs to continue running the app on each device and
set other breakpoints as necessary.
When you’re done, press the Stop icon in the toolbar and select the option to
Stop All processes. This will disconnect the debugger from both devices.
Now that you have devices connected for debugging, you’re ready to start
capturing data from them.
Screenshots
One of the most common capturing tasks you’ll need to perform is taking a
screenshot of the device. There are a few different ways to take a screenshot of
an Android device:
20
Android Debugging by Tutorials Chapter 1: Getting Started
You’ll need to simultaneously press and hold power and volume-up buttons to
take a screenshot from most Android devices. A “snap” will occur when you do
this, and a shutter sound will play, indicating that you took a screenshot.
Run the application on the device by pressing the Play icon in the AS toolbar. Go
to the Logcat window by selecting View ▸ Tool Windows ▸ Logcat.
3. Click the Screen Capture camera icon in the left column of the window.
A new dialog window will appear with the screenshot from the device, and you
can save the image to your workstation.
Similar to the Logcat screenshot approach, a dialog will appear where you can
save the screenshot to your workstation.
Recording Videos
Within the Logcat window, you can also record videos of your device. Press the
Screen Record video recorder icon in the left column of the Logcat window.
21
Android Debugging by Tutorials Chapter 1: Getting Started
In the Screen Recorder Options dialog, you can choose the bit rate and
resolution you prefer for the recording or use the defaults provided. The dialog
also contains options to show screen taps and set the video format if you’re
using an emulator.
Click Start Recording to start the recording. You’ll see a new dialog showing
that the recording is in progress.
At this point, you can perform the necessary actions you want to have recorded
on your running device or emulator. When you’re done, just use the Stop
Recording button to stop the video. You’ll then be prompted to save the
recording to your workstation file system.
Note: There is a lot more to the Logcat tool than just screenshots and videos!
You’ll learn more about all the features that Logcat has to provide in Chapter
3, “Logcat Navigation & Customization”.
To generate the report on the device itself, you’ll need to have enabled
Developer Options. Go to Settings ▸ System ▸ Developer Options, or Settings
22
Android Debugging by Tutorials Chapter 1: Getting Started
▸ Developer Options on some devices, and tap Bug Report.
In the Bug report dialog, select the type of report you want.
Tap Report when you’re ready to have the system generate the bug report.
While the report is generating, you’ll see a notification appear in the status bar.
This notification provides information about the progress of the report. When
the report completes, you’ll see another notification appear with the option to
share the bug report.
23
Android Debugging by Tutorials Chapter 1: Getting Started
While sharing bug reports from devices is extremely useful, if you don’t yet have
a bug report but can reproduce a specific error or crash you can generate a bug
report directly from an emulator and save it to your workstation.
To generate a report from an emulator, click the More option in the emulator
window.
In the Extended Controls dialog, click Bug Report in the left column. Here, you
have the option to add a screenshot, provide steps to reproduce the bug and
include the bug report itself.
24
Android Debugging by Tutorials Chapter 1: Getting Started
Select Save Report, and you’ll be prompted to save the bug report folder on
your workstation.
Now that you have a bug report, it’s time to inspect it!
25
Android Debugging by Tutorials Chapter 1: Getting Started
Next, you’ll take a look at the first file mentioned, the actual bug report.
With ANRs, you can typically associate the logs with the specific ANR stack trace
text files typically found in FS/data/anr/ . These stack trace files can help you
better understand what caused the ANR depending on the thread associated
with the stack trace file itself. For example:
This stack trace shows that there was an ANR that occurred on the main thread,
specifically in DashboardFragment of the Settings app.
26
Android Debugging by Tutorials Chapter 1: Getting Started
Installing ADB
adb commands should work out-of-the-box as long as you have Android
Studio installed. Android Studio comes bundled with adb as part of the
platform-tools package in the bundled Android SDK. If you receive a
“command not found: adb” error when attempting to run adb , then you can
either download the platform-tools package directly or install it via Android
Studio.
27
Android Debugging by Tutorials Chapter 1: Getting Started
Now check the Android SDK Platform-Tools checkbox and click OK.
Once you download the package, set your PATH environment variable to
include <android_sdk_location>/platform-tools . Now you’re ready to use
adb !
First, open the Terminal in Android Studio and try out the following commands:
Start ADB:
$ adb start-server
Stop ADB:
$ adb kill-server
If there are any ADB connectivity issues, it is best to try to stop and restart the
ADB server process first to see if that fixes things.
$ adb devices
List of devices attached
adb-R58MA3DJGKP-nC7KH1._adb-tls-connect._tcp. device
emulator-5556 device
$ adb kill-server
$ emulator -avd Pixel_5_API_30 -port 5557
$ adb start-server
28
Android Debugging by Tutorials Chapter 1: Getting Started
$ adb devices
List of devices attached
emulator-5557 device
The code above launches the emulator named Pixel_5_API_30 on the 5557
port.
Installing an app:
Notice the shell option used in some of the previous adb commands?
You can use shell by itself too, and run shell commands directly on the
device:
29
Android Debugging by Tutorials Chapter 1: Getting Started
$ adb shell
generic_x86:/ $ pwd
/
generic_x86:/ $ cd /sdcard
generic_x86:/sdcard $ screencap /sdcard/screen.png
generic_x86:/sdcard $ exit
$ adb pull /sdcard/screen.png ~/Downloads/screen.png
Challenges
Here are some great commands for getting information from a bug report:
To view activities that were in focus to the user and that the user interacted
with:
30
Android Debugging by Tutorials Chapter 1: Getting Started
To make these commands work, you need to modify them a bit by changing the
bug-report file name. Good luck!
You can do a lot more with just the bug report text file. The commands
mentioned just scratch the surface on unlocking your full debug potential.
Try throwing an exception in the Podplay starter or final project and see if you
can generate a bug report and find the stack trace within it.
Key Points
Run an app in debug mode to investigate issues in your app.
ADB is a powerful tool to use, and this chapter only demonstrates the tip of the
iceberg. Read the Android Developers ADB user guide to learn more about all
the commands provided by ADB.
31
Android Debugging by Tutorials
Breakpoints are the backbone of any good IDE and a powerful tool in a
developer’s toolbox; Android Studio is no different!
If you’ve worked with any Android code before, you’re sure to have used these
useful little red circles many times to help locate a bug or simply to try and get a
better understanding of your code.
You might think that you know all there is to know about breakpoints in
Android Studio, but just breaking on a code line only scratches the surface of
how powerful breakpoints can be.
This chapter will teach you how to become a breakpoint power user.
You’ll learn:
Add a breakpoint on the first line within onCreate() by clicking inside the left
column next to the line of code that states
32
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
super.onCreate(savedInstanceState) .
A red circle will appear on that line in the column, like this:
You’ve now created a suspended breakpoint on that line of code. Your app’s
execution will pause when it gets to that line inside onCreate() .
Run your app in debug mode by going to the Android Studio toolbar and
clicking the Debug button:
Your app will load and pause on the breakpoint that you just set. Android Studio
will highlight the line of code that it’s currently paused at:
This indicates that the code has paused before executing this specific line. You
can’t interact with your app as it’s currently frozen in a suspended state.
33
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
This is your command center for debugging; it’s where you can observe your
variables, threads and logs. It’s also where you can execute debugging
commands such as stepping through your code’s execution line by line after it’s
suspended on a breakpoint.
The Debug Window will automatically appear at the bottom of your Android
Studio window when a breakpoint is hit, and it’ll look like this by default:
If you don’t see this window, toggle it by going to View ▸ Tools Windows ▸
Debug.
34
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
1. Show Execution Point: Clicking this button will focus Android Studio on
your current execution point. If you navigate away from your execution
point while your program is paused to investigate another class or method,
this button will re-orientate you back to your execution line.
2. Step Over: This executes the currently highlighted line of code and moves
on to the following line within the current method scope.
3. Step Into: By clicking this button the debugger steps into the method on the
execution line and starts executing its logic along with switching the scope.
The Frames pane will open a new stack frame, and your execution line will
move into that method. You’ll learn about this in the next section.
4. Force Step Into: When running Step Into, Android Studio will usually only do
so when the function that you’re stepping into is your own code. Force Step
Into will tell Android Studio to step into the method no matter what, even if
the source code can’t be fully indexed. You can learn more about indexing at
https://www.jetbrains.com/help/idea/indexing.html.
5. Step Out: This will do the opposite of Step Into. The debugger steps out of the
current scope, moving one level higher.
6. Drop Frame: Similar to Step Out, although you’ll return to the code line
before the frame began instead of after the frame has completed execution,
effectively time traveling back in your program.
7. Run to Cursor: This will resume executing the program until it reaches the
line of code wherever your cursor is placed (as if there was a breakpoint on
that line).
Play: Resumes executing your program until it reaches the next breakpoint.
Next, to put these buttons into practice, you’ll navigate your code using them.
35
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
In the previous step, you entered debug mode with your code execution paused
on the super.onCreate(savedInstanceState) line inside onCreate() ; if
you’re not in debug mode, go to the AS toolbar, click the Debug button again
and get back to that breakpoint.
Click Step Over four times. Your code execution line moves down within the
onCreate scope and ends up on line 5 of the function: setupViewModels() .
Now, step into the highlighted method by clicking Step Into. Your code
execution moves to the first execution line of setupViewModels() , like so:
Use the Step Out button and you’ll end up back inside the onCreate scope after
setupViewModels() has executed.
36
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
Click Run to Cursor, and your execution will resume until it reaches that line.
This is a handy button to use if you want to quickly navigate to a line of code
without setting a breakpoint there.
Great job! You’ve learned the basics of code navigation in debug mode. Click
Stop to stop your debug session.
Next, you’ll learn how about stack frames and how to drop them.
Relaunch the app by clicking Play and subscribe to two podcasts so that they
appear on the home screen of your app. Do this by tapping the search icon in
the app bar, entering a phrase such as “science” and pressing the Return key on
your keyboard to start searching for results:
37
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
Do this two or more times until you have a selection of subscribed podcasts in
your home screen view:
38
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
Run your app in debug mode and your code execution will pause on that line:
Now, bring your attention to the Frames pane inside the Debug Window:
39
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
Each frame is a list item that represents the scope of a method. The frame
you’re viewing is highlighted in blue; right now, it’s the frame of
showSubscribedPodcasts() ; the scope where your code execution has paused
at your breakpoint.
Frame list items show useful information that dictates where it’s located:
Here is a breakdown:
2. 163: The code line number where the code execution is paused within that
frame.
3. PodcastActivity: The code’s class.
On the right of the Frames pane, you’ll see the Variables pane. This pane shows
the visible variables available to the current frame.
Click the chevron next to this in the Variables pane to see a list of all of the
variables and values available to the frame’s context.
Your code moves down the scope, executing getPodcasts() from the view
model. Now your Variables pane shows a podcasts variable which is only
visible to the active scope of showSubscribedPodcasts() . Expanding the list
using the chevron again will show you the podcasts that you subscribed to
earlier:
40
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
Imagine for a second that you accidentally clicked Step Over and you wanted to
debug getPodcasts() ; by stepping over, you’ve executed that method without
going into the scope via Step Into.
There is still a way to step into getPodcasts() without restarting the app by
using the Drop Frame feature.
Look at the Frames pane and notice that the previous frame in the stack is a
frame with the setupPodcastListView ’s scope. Click that frame and focus on it
to expand the frame box:
As you learned earlier in this chapter, the Drop Frame action stands next to the
Step Out action. Now, use Drop Frame and click Step Over once.
Magic!
You’ve traveled back in time in your program’s execution to the first line of
showSubscribedPodcasts() .
41
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
The Drop Frame button can save a lot of time when debugging as, in most use
cases, it means that you don’t have to restart your app.
The way it works is by caching each of the frames so that they can be reloaded
when required. When using this feature, bear in mind that if you were in the
middle of a long function that had done a lot of intermediate work, for example,
modifying the state of the current class, that wouldn’t get undone when you
drop the frame; so use Drop Frame with that caveat in mind!
Conditional Breakpoints
Until this point, you’ve learned how to mark a code line with a default suspend
breakpoint that simply pauses your code execution when it’s hit.
Now, you’ll look at some more advanced features of breakpoints; features that
let you control when the breakpoint is hit and what it should do when that
happens.
You’ll need some more tricky code to debug for this section. Remove your
existing breakpoints and stop your code from executing by clicking Stop.
Right now, you want to debug the logic of PodcastListAdapter when the user
searches for podcasts.
Run your app in debug mode and wait for your program to reach the
breakpoint.
42
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
The code shouldn’t have paused here; you haven’t entered a search term yet.
You might be wondering what is going on.
What happened is that the PodcastListAdapter is being used in two places; the
first is for displaying the user’s list of subscribed podcasts, and the second is for
showing the podcast results of a search term.
Dependent Breakpoints
To solve the problem of code reuse, you can use a condition called breakpoint
dependency. This is where we can set up a breakpoint only to be enabled if a
previous, different breakpoint has been hit.
Note: if you aren’t a fan of scrolling through large classes like this one, you
can press Shift twice to search for terms in your project. Just type the method
name in the search box to save yourself some time.
This method updates the PodcastListAdapter with podcast search results with
43
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
this line of code:
podcastListAdapter.setSearchData(results)
Place a breakpoint here. You don’t want the code execution to actually pause on
this line of code, so here is where you’ll use a new type of breakpoint: An
unsuspended breakpoint.
An unsuspended breakpoint won’t pause your code execution when hit; this
can be used for many practical use cases, one of them being logging, which
you’ll get to later. For now, you’ll simply use this as a trigger for enabling your
other dependent breakpoint.
To use this type of breakpoint, right-click it and select More at the bottom of the
breakpoint window:
This will open up a lot more settings for your breakpoint. For now, uncheck the
box that says Suspend:
44
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
45
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
This indicates that it’s currently not enabled. It’ll become enabled and, in turn,
switch to solid red when the debugger reaches the dependent breakpoint in
PodcastActivity .
Rerun your project in debug mode. Now, when you launch the app, your code
won’t stop executing.
Start searching by tapping the search icon in the app bar and inserting a podcast
name. After you confirm the search term, your execution will break
successfully:
46
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
You set the new position after the summaryItem variable has been populated, so
you’ll be able to use that data in a breakpoint code condition.
Open the settings of your breakpoint by right-clicking it and then clicking More.
Check the box that says Condition: and in the input label underneath, type in
this code:
Now, your code won’t suspend unless both the dependent breakpoint and code
47
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
conditions are met. So it should only suspend if you are in the search view and a
podcast whose name equals Android Developers Backstage is being populated by
the adapter.
Try searching a few search terms to verify that the app code does not pause
execution; then input “Android” into the search field.
Your code suspends as the adapter is attempting to populate the list item for the
podcast Android Developers Backstage:
Breakpoints Window
Most engineers set and remove breakpoints as they need them, but a better way
is to manage a more extensive set of breakpoints using the Breakpoints
window.
1. Using Android Studio Toolbar - You can the Breakpoints window by clicking
Run ▸ View Breakpoints….
2. Right-clicking an existing breakpoint to access its settings and selecting
More.
48
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
The Breakpoints window is a hub for all of your programs breakpoints; you can
go to any that you have set, update conditions and enable or disable them all
from this window.
Within the Breakpoints window, you can create groups to store a certain set of
breakpoints. These groups can then be disabled and enabled when required.
As a scenario, pretend that the Android Developers Backstage podcast had a bug
that you were trying to find and fix. A group would be ideal for storing all of the
breakpoints related to this bug.
49
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
Type in the name “Android Developers Backstage Bug” and click OK.
You can toggle these breakpoints on and off by clicking the checkbox next to the
50
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
new group. This allows you to continue to work on other activities within your
project and come back to bugs at a later time without having to reset all of the
necessary breakpoints.
Your breakpoint will now never suspend the code execution as you’ve disabled
its suspend function; instead, each time it’s hit, a “Breakpoint hit” log will be
posted to the debug window, as well as the stack trace.
Search “Android Backstage” in the app by tapping the search icon and then
clicking the Debug tab to see the logs that have been emitted:
51
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
You also have the power to log any information that you like from breakpoints,
not just the stack trace.
As long as your breakpoint has access to the variables needed in its scope, you
can input any type of log code that you need into the Evaluate and log field,
logging as little or as much as required. In this case, the breakpoint is logging
some useful information, and the podcasts feed URL.
You’ll be prompted to add an import for Log . Go ahead and follow the tooltip
prompt to add the import.
Note: If you don’t see the tooltip prompt, set your cursor to Log and press
Option-Return to add the import.
Run your app in debug mode and search for “Android Backstage”. Your Debug
52
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
tab will now show your custom log message when it’s hit:
Key Points
Navigate a suspended app by stepping through code with Step Over, Step Into
and Step Out.
Use the Frames pane to navigate to different scopes within your current
execution stack.
Use Drop Frame to go back in time to a previously executed stack.
Use breakpoint conditions such as depends on and code conditions to
control when a breakpoint should register that it’s been hit.
Use unsuspended breakpoints for breakpoints that shouldn’t pause the code
execution.
You might be surprised to hear that there are many additional advanced
features that breakpoints have that go above and beyond the average debugging
requirements of an engineer and can handle the trickiest of bugs; these include
functions such as:
Pass count that enables a breakpoint only after it’s been hit a certain
53
Android Debugging by Tutorials Chapter 2: Navigating Your Code With Breakpoints
number of times.
Method, field and exception breakpoint types that can be used in addition to
line breakpoints.
Read about these advanced features and even more in the excellent IntelliJ
documentation for Breakpoints.
54
Android Debugging by Tutorials
The Logcat window in Android Studio acts as a central hub for displaying
system and custom messages of applications across Android devices connected
via the Android Debug Bridge - ADB.
With the power of the Logcat window, you can view messages in real-time and
historically, allowing you to understand exactly how your apps function by
viewing the logs they emit.
In this chapter, you’ll discover how to utilize the Logcat window to its fullest
potential as you navigate PodPlay and fix a selection of bugs by emitting and
reading logs. You’ll learn how to:
Open the Podplay starter project in Android Studio and run PodPlay. Complete
these actions inside the app:
4. Tap the play icon and wait for it to load and start playing.
5. Pause the podcast.
In Android Studio, expand the Logcat window by clicking View ▸ Tool Windows
▸ Logcat.
55
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
You can also expand and minimize the Logcat window by clicking the Logcat tab
at the bottom of Android Studio:
There’s now a whole bunch of logs that your Android device has produced; it
can be pretty daunting at first sight as there are so many! You’re going to work
through it bit by bit.
3. Log level - A quick filter that toggles between different log severity levels.
4. Filter search bar - This allows filtering the logs displayed based on text or
regex.
5. Filter menu - Use this to filter the logs based on the source of executing
process.
For now, make sure that you have Show only selected application in the filter
menu:
56
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
Your Android device will emit all of your system logs if you don’t have this
selected, even if your active app isn’t producing them. In most cases, you’ll want
this filter on.
Logs that are output by an Android application can be of six different severity
levels:
1. Verbose - The lowest severity level log; this shouldn’t compile these logs into
an application outside of development; choose this filter if you want to see
every log level.
2. Debug - Logs that are useful only during development; they should be
stripped from release builds.
3. Info - General informative log message for standard usage, these are
intended to be kept in a release environment.
4. Warn - Shows possible issues that could become errors; these are always
kept in a released app build.
5. Error - Logs that emit due to errors in the application; these will be displayed
in a release build.
6. Assert - Logs that should never be shown; if they are, something has gone
critically wrong with the app.
Setting the log level filter like this tells Logcat only to display logs that are the
level of Warn or higher. (The log levels higher than Warn are Error and Assert.)
57
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
Logs that are warnings generally appear as an app launches. This is due to the
nature of warnings. A program compiles anything that the program doesn’t
think is set up perfectly and may cause a future error only once.
In this particular case, the emitted logs are due to the selected emulator and
outdated code it uses within the Android open source project libraries, you can
ignore them. With Android updates, most of the existing warnings disappear,
and new ones may show up. It takes a keen eye to spot a warning that’s due to
your own code; you should fix these as soon as they’re spotted.
2. The identifier of the process and thread that sent the log.
3. Package ID of the app that sent the log.
5. The log tag that helps identify where the log came from; usually the class
name.
6. The log message itself appears last.
This is the format for every log, no matter the log level.
With the log severity level decreased, you’ll see some additional Info logs that
58
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
have appeared in the Logcat window. These are general information logs that
tell you useful information about your running processes.
In this example, there are new Info logs telling you that the Java garbage
collector has freed up some memory and another that tells you the number of
windows and views displayed.
Go back to the Log level drop-down again and select the Debug filter:
Finally, enable all of the logs by selecting Verbose in the Log level drop-down:
The Logcat fills up with a lot more logs with the Verbose level enabled. You can
see logs detailing things like the app’s API connections:
59
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
Now that you’ve enabled all the logs, you’re able to see a lot of system
information that tells you what the app is doing at each moment.
However, this information is pretty useless if you’re not looking at the logs in
real-time, as you have no way of linking the system logs to what actions are
being completed within the app. This is because you’re not sending any custom
logs.
Next, you’ll go through the process of adding custom logs throughout the
PodPlay code so that you can easily interpret what PodPlay is doing at each
moment from the Logcat.
To send logs to the Logcat, there is a utility class in Android named Log. You’ll
use this class to set your custom log severity levels, tags, and messages.
In the starter project, open PodcastActivity.kt and add the Log import to the
top of the file next to all of your other imports:
import android.util.Log
60
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
This will be the tag you’ll use for all of the custom logs that you’ll send within
PodcastActivity . You can, of course, set tags to anything. As a default, it’s best
practice to have your custom log tag as the class which has sent the log so you
can easily map the location.
Info Logs
When you’re viewing logs, the main piece of information that you need to know
is where the app code is during an event. To do this, you can decorate your code
with Info logs that simply state what has been called.
The Log class has a number of different static methods which dictate the logs
severity level, the very same severity levels discussed earlier in this chapter:
Log.v(): Verbose.
Log.d(): Debug.
Log.i(): Info.
Log.w(): Warning.
Log.e(): Error.
Each method takes a tag and message String parameter as well as an optional
Throwable parameter that logs an exception.
Here, you are again logging the information that you’ve called onNewIntent() .
You’re also passing the action of the Intent within your log’s message string.
It’s always best to include any relevant information in your logs that may be able
to help with debugging your code.
61
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
You’re leaving more detailed information that you’re about to retrieve a podcast
feed.
Now, build and run the app again, ensuring you have the Logcat window
expanded. Switch the severity level filter to Info. You only want to see the logs of
severity level Info and higher right now.
You’ll see right away that you’re now getting an “onResume() called” log appear
in the Logcat window:
In PodPlay, tap on the search icon and enter the term test:
62
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
Tap on the first podcast that you see and observe the logs:
You’ll now see it sending additional custom logs that inform you’re calling
onNewIntent() and that it is retrieving a podcast feed.
With only sending these three additional logs, the Logcat window has become a
lot easier to read as, in between the system logs, there are details of what the app
is doing at any given time.
Debug Logs
Now that you’ve added some Info logs and seen the clarity that they can add to
the Logcat, you’ll gain even more insight by adding debug logs that will inform
the Logcat reader of the speed at which it can retrieve a podcast’s information.
Also, add the Log utility method import at the top of the file:
import android.util.Log
Now, scroll down to the method getPodcast() and replace // TODO 3 with a
new custom log:
You’re setting this log’s severity level to Debug as you’ll use this to debug how
long it takes to retrieve a podcast from the repository. This log states the
podcast name that the app is about to retrieve data for; it also states that it’s
recording the time.
63
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
Next, you’ll need to measure the actual time to log this in a message when the
call is complete. You’ll use the Kotlin system method measureTimeMillis() to
do this.
To be able to use the method, add it to the imports section at the top of the file:
import kotlin.system.measureTimeMillis
Switch the Logcat severity level to Debug, then build and run the app to see this
new log in action.
Search for a podcast with the term test by tapping on the search icon and then
tap on the first podcast in the results list.
You’ll receive some awesome debug information that details how long it takes
the app to retrieve a podcast’s details:
64
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
When the Logcat window emits many logs, it can become quite cumbersome; it’s
hard to navigate and locate the logs you care about quickly. There are ways to
improve this with customization and additional filtering.
Click this cog and a Configure Logcat Header dialog will appear:
You can choose what information you’d like to include in each log using this
dialog.
Go ahead and uncheck Show date and time, Show process and thread IDs
and Show package name:
65
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
Click OK.
Your logs now won’t have all of the prefixed metadata, like package and thread
ID, making the logs simpler and easier to read.
Note: You can re-enable any prefixes by going back to the Logcat Header
setting and checking the relevant boxes.
Folding Logs
Folding is another tool that you can use to partially remove logs that aren’t
important to you from view. It’s collapsing some data into a condensed view, so
it takes up less screen real estate; you can then usually unfold or expand the
collapsed view with a click.
You can use this method of folding with Android code by collapsing scopes like
for loops and if statements; you can even collapse whole classes.
You can do this with logs too! Find a log that you would rather fold from view,
right-click and select Fold Lines Like This:
This will open a console dialog where you can add, remove or edit the regex
pattern of logs you want to fold:
66
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
You can add any type of text to this list. If it matches the results of a log, it will
fold. To demonstrate this, input a new line by clicking the plus icon and entering
the text Accessing hidden method.
Click OK.
With the Verbose severity level set, scroll up in the Logcat window, and you’ll
see folded logs that state <3 internal lines>:
67
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
Clicking the fold icon to the left of these logs will collapse them again:
By setting up your fold filters appropriately, you can get to a state within the
Logcat window where only logs you would actively like to view begin in an
expanded state, whereas all others are folded.
The search bar can match exact text for the logs you’d like to find, with the
ability even to match regex patterns for more advanced searching.
Every part of a log is searchable, including the tag. Searching for tags can be a
powerful and quick way to find the logs you’re looking for instantly. Add
PodcastActivity into the search bar to quickly find all your logs with that tag
that have:
You may want to save a search that you find yourself repeatedly using so that
you can quickly select it. You can do this with Filter Configurations located to
the right of the search bar.
68
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
down:
In Edit Filter Configurations, you have access to an even more powerful set of
search settings that allow you to combine multiple search results into one filter.
Add a permanent filter configuration for viewing your custom logs so you can
easily toggle it to on in the Logcat window.
In the Filter Name input field type PodPlay Custom Logs, and in the Log Tag
input field add the regex PodcastActivity|PodcastViewModel:
Click OK.
This will create a new filter that searches for all logs that have the tag of either
PodcastActivity or PodcastViewModel. You can select this filter, or a different
one, by going back to the Filter Configurations drop-down:
69
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
Launch PodPlay and make sure that you have the Logcat window open to see
emitting logs while navigating the app. Set the Log level filter to Info and make
sure you’re not using the custom filter configuration:
Search for a podcast by inputting Science into the search window of PodPlay
and tap on the first podcast in the list of results. Then, select the first podcast
episode. You’ll now be on an episode with the ability to play the podcast.
Press the Play icon and observe the lag between pressing play and the podcast
actually playing.
This is strange behavior; something is clearly misbehaving, the Logcat can help
identify the problem.
Open the Logcat window, and you’ll see that an Info log with the tag of
Choreographer has been output:
“Skipped 42 frames! The application may be doing too much work on its main
thread.”
This log appears exactly as you press the Play icon for a podcast episode but not
on subsequent playing after pausing the episode.
Interesting!
Armed with this information, you know that something is wrong; something is
blocking the main thread and causing lag.
The next step is to add custom logs to PodplayMediaCallback so you can locate
the exact method that is blocking the main thread.
70
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
Open PodplayMediaCallback.kt. Above the class declaration add a constant
called TAG:
Now add the Log utility import to the top of the file so you can use it throughout
this class:
import android.util.Log
To locate the offending method, you’ll need to add custom logs that inform you
of when it calls each method to get more clarity on what PodPlay is doing when
the bug occurs.
And finally, inside startPlaying() replace // TODO 11 with another Info log:
71
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
You’ve now added six Info logs to PodplayMediaCallback . These will give you a
lot more context when trying to locate the bug that’s blocking the main thread.
Build and run PodPlay and, once again, navigate to a podcast episode and play it
by pressing the Play icon.
Take a look at the logs; you can see that the Logcat is emitting each Info log and
the skipped frames system log appears at the end. This doesn’t tell you much
about the exact method causing issues, but you may have noticed that one of
the methods took longer to execute.
If you don’t see the log times, enable the time Logcat header, Show date and
time, by clicking the Configure Logcat Header settings icon, then look at the
logs again:
You can see that a larger than average amount of time passed between
prepareMedia() and startPlaying() .
CoroutineScope(Dispatchers.IO).async {
prepareMedia()
}.invokeOnCompletion{
startPlaying()
}
This requires adding three additional imports to the top of the file:
72
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
Now, if you reinstall the app and try playing an episode, you’ll notice that the
“skipped frames” is no longer displayed, indicating that the main thread is not
blocked. You’ve successfully fixed a bug by observing the Logcat.
Challenge
A frustrating situation happens when you attempt to click the podcast series
called Early Middle Ages; it doesn’t do anything!
The Logcat window is sparse when clicking this podcast; it doesn’t log any debug
or info logs that state what is wrong. It’s up to you to change that in this
challenge.
Use what you’ve learned so far with breakpoints and logging to find out why this
bug is happening and implement a viable solution.
Go to the challenge or final projects for this chapter if you need help locating
this bug.
Key Points
Logs can be of different severity levels: Verbose, Debug, Info, Error, Warn and
Assert.
You can configure the Logcat window to make it easier to read by clicking the
Logcat Header icon.
Fold logs that you don’t care about by right-clicking it and selecting Fold
Lines Like This.
Send custom logs from the app using the Util Log method.
Decorate the app with Info logs so it can debug the Logcat window more
easily.
Add custom filer configurations by clicking Edit Filter Configurations in the
Logcat window.
73
Android Debugging by Tutorials Chapter 3: Logcat Navigation & Customization
If you’re interested in discovering more about this topic, check out the Android
documentation about Logcat.
Don’t stop here. In the following chapter, you’ll find out how to deal with stack
traces and how to analyze them.
74
Android Debugging by Tutorials
In the previous chapter, you learned about Logcat and how you can use the
Logcat window to find bugs in your app. One common example of Logcat is
finding stack traces for crashes and exceptions.
Analyzing a stack trace, and understanding how the trace itself is structured,
provides clues as to why the app encountered the error, to begin with.
In this chapter, you’ll learn how to read a stack trace from the Podplay app, use
tools to navigate through it and fix the associated bug behind the trace. You’ll be
able to:
For example, you may have the following methods, a() , b() and c() :
fun a() {
b()
}
fun b() {
c()
}
fun c() {
throw Exception("Uh oh!")
}
When the exception throws, the stack trace will contain the methods with the
75
Android Debugging by Tutorials Chapter 4: Analyzing the Stack Trace
most recent method call at the top:
c()
b()
a()
Stack traces in Android will also include line numbers to denote where the next
function call is in the stack.
Next, you’ll learn how to read and utilize stack traces to fix a bug in the Podplay
app.
Once the list of podcasts appears, tap the first podcast in the list. The app
crashes!
Open the Logcat window in Android Studio, switch to the Error type, and find a
stack trace similar to the following:
76
Android Debugging by Tutorials Chapter 4: Analyzing the Stack Trace
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.yourcompany.podplay, PID: 22519
java.text.ParseException: Unparseable date: "Fri, 11 Feb 2022
02:10 GMT"
at java.text.DateFormat.parse(DateFormat.java:362)
at
com.yourcompany.podplay.util.DateUtils.xmlDateToDate(DateUtils.kt:5
6)
at
com.yourcompany.podplay.repository.PodcastRepo.rssItemsToEpisodes(P
odcastRepo.kt:75)
at
com.yourcompany.podplay.repository.PodcastRepo.rssResponseToPodcast
(PodcastRepo.kt:88)
at
com.yourcompany.podplay.repository.PodcastRepo.getPodcast(PodcastRe
po.kt:61)
at
com.yourcompany.podplay.repository.PodcastRepo$getPodcast$1.invokeS
uspend(Unknown Source:15)
at
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(Cont
inuationImpl.kt:33)
at
kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at
android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(Runtime
Init.java:492)
at
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
The E/AndroidRuntime part denotes that this was an error log with
AndroidRuntime as the tag. You’ll also see that the exception occurred on
the main thread.
77
Android Debugging by Tutorials Chapter 4: Analyzing the Stack Trace
The third line details what type of exception was thrown, along with an
associated message attached to the exception. In the case above, the
ParseException , was thrown because the date provided isn’t parsable.
4. The remaining lines detail the call stack. Each following line represents a
method, and the associated line number is provided. The line numbers from
the previous method in the stack correlate to the method call above it. For
example:
at java.text.DateFormat.parse(DateFormat.java:362)
at
com.yourcompany.podplay.util.DateUtils.xmlDateToDate(DateUtils.k
t:56)
Knowing where the crash occurs in the code is crucial to fixing the underlying
problem. Now that you know what is causing the crash, it’s time to fix it!
Note: Catching exceptions isn’t always necessary. It may make more sense to
fix the crash in certain situations. For example, if you encounter a
NullPointerException you may need to add a null check to your code to
prevent the offending line of code from being reached if a null value is
provided.
On that line, hover your cursor over parse() , and the code documentation for
that method appears. If the code documentation isn’t displayed, move your
78
Android Debugging by Tutorials Chapter 4: Analyzing the Stack Trace
cursor to the method and press F1. The code documentation explains that the
method will throw a ParseException when the date formatter can’t correctly
parse the provided date text.
Now that you know parse() can throw an exception, it’s time to handle it.
Replace line 56 by wrapping the logic inside a try/catch block:
return try {
inFormat.parse(date) ?: Date()
} catch (e: ParseException) {
Log.wtf("xmlDateToDate", e)
Date()
}
The return statement takes the entire try/catch block. If you can parse the
date string, or if parse() returns null , a Date object will be returned in the
try block.
But, if parse() throws a ParseException , your code will now catch this. First,
the exception will be logged using the Log.wtf() (which stands for “What a
Terrible Failure” of course!) providing the custom error tag of “xmlDateToDate”
and the exception e . Then, you set a new Date instance as a return value.
Providing a Date object even in the catch block ensures that the app can
continue moving forward.
Rerun the app and follow the steps noted earlier to try to reproduce the crash.
This time there’s no crash!
Open the Logcat window in Android Studio, and you’ll find the exception:
79
Android Debugging by Tutorials Chapter 4: Analyzing the Stack Trace
01:25 GMT"
at java.text.DateFormat.parse(DateFormat.java:362)
at
com.yourcompany.podplay.util.DateUtils.xmlDateToDate(DateUtils.kt:5
9)
at
com.yourcompany.podplay.repository.PodcastRepo.rssItemsToEpisodes(P
odcastRepo.kt:75)
at
com.yourcompany.podplay.repository.PodcastRepo.rssResponseToPodcast
(PodcastRepo.kt:88)
at
com.yourcompany.podplay.repository.PodcastRepo.getPodcast(PodcastRe
po.kt:61)
at
com.yourcompany.podplay.repository.PodcastRepo$getPodcast$1.invokeS
uspend(Unknown Source:15)
at
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(Cont
inuationImpl.kt:33)
at
kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at
android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(Runtime
Init.java:492)
at
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Notice that in the first line the tag now shows up as E/xmlDateToDate rather
than E/AndroidRuntime . The stack trace still exists for reference, but the app
successfully recovered from the thrown exception.
Wouldn’t it be great, though, if crash data from real users came to you rather
than manually searching through the code or trying to reproduce crashes in
order to get the stack trace? Well, there’s a way to do that. Next, you’ll learn how
Firebase Crashlytics can provide some automation around tracking crashes.
Firebase Crashlytics
Crashlytics is an invaluable tool that provides you with crash reports, non-fatal
errors, and Application Not Responding (ANR) errors. Firebase is Google’s
suggested app development platform which hosts Crashlytics as a service, along
with several other great tools.
Next, you’ll learn how to set up a Firebase account and connect it to the Podplay
80
Android Debugging by Tutorials Chapter 4: Analyzing the Stack Trace
project. You’ll then use Crashlytics to view crashes that occur in Podplay.
Next, enter the project name. It can be Podplay, similar to the app, or anything
that’ll help you correlate the Firebase project to your app. Check the
confirmation checkbox and select Continue.
The next step will ask you to enable Google Analytics for the Firebase project.
Crashlytics will use Google Analytics to determine crash-free user data. Make
sure the toggle is on, and move forward using Continue.
81
Android Debugging by Tutorials Chapter 4: Analyzing the Stack Trace
In the last step, choose the Google Analytics account to which you want the
Firebase Project linked. Then click Create project.
The project gets created, and you should see a loading indicator while the
project finalization wraps up.
82
Android Debugging by Tutorials Chapter 4: Analyzing the Stack Trace
You’ll redirect to the new Firebase project’s dashboard. Now it’s time to add
your app to the Firebase project.
The first step is to register your app. Enter in the package name of the Podplay
app: com.yourcompany.podplay
83
Android Debugging by Tutorials Chapter 4: Analyzing the Stack Trace
Also optional is the debug signing certificate SHA-1. This is useful for securing
the Firebase project by only allowing app builds signed with the debug key on
your workstation.
To get the SHA-1 of the debug certificate, open a Terminal window and navigate
to the home directory on your workstation. Next, run the following command:
keytool -list -v \
-alias androiddebugkey -keystore .android/debug.keystore
If prompted for a password, enter android. Then, keytool will print out the
certificate fingerprints for the androiddebugkey alias. Copy the SHA-1
fingerprint and paste it in to the form field.
Next, click Register app. It’ll prompt you to download the google-
services.json file and add it to the app/ folder. This file provides all the
configuration data the app can use to communicate with the Firebase project.
84
Android Debugging by Tutorials Chapter 4: Analyzing the Stack Trace
Back on the Firebase console’s app registration page, select Next. You’ll need to
update your project-level and app-level build.gradle to include the Firebase
dependencies.
Open the project-level build.gradle first, and add the following class paths to
the dependencies list:
classpath 'com.google.gms:google-services:4.3.10'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.0'
Note: The Firebase setup also mentions the need to add the google()
maven repository, but these have already been added to build.gradle.
Now, open the app-level app/build.gradle. Add the following plugins below the
list of other applied plugins:
85
Android Debugging by Tutorials Chapter 4: Analyzing the Stack Trace
apply plugin: 'com.google.firebase.crashlytics'
Add the imports for the Firebase BoM, bill of materials platform, and specify the
usage of the Firebase Crashlytics and Analytics libraries:
implementation platform('com.google.firebase:firebase-bom:30.0.1')
implementation 'com.google.firebase:firebase-crashlytics-ktx'
implementation 'com.google.firebase:firebase-analytics-ktx'
Note: With the use of the Firebase BoM platform, you don’t need to specify
the versions on the individual libraries as the platform is aware of what
versions to use.
Click the Sync project with gradle files icon in the toolbar to pull down the
Firebase SDK into the project.
Now, switch back to the Firebase web page and click Next. You’re all done with
the setup! Choose Continue to console to return to the Podplay Firebase
project’s dashboard.
86
Android Debugging by Tutorials Chapter 4: Analyzing the Stack Trace
You’ll see that the Crashlytics page is waiting for the app’s first crash.
Now you need to crash the app. In Android Studio, open PodcastActivity.kt and
add the following line in onCreate() right after the super.onCreate() call:
Run the app and go back to the Firebase project web page. You’ll see that it has
detected the crash and that the installation is complete! Click Go to Crashlytics
dashboard.
87
Android Debugging by Tutorials Chapter 4: Analyzing the Stack Trace
Scrolling down a bit, you’ll see the PodcastActivity.kt crash listed as a “fresh
issue”.
Click the issue to view the details page. Scroll down to the Event summary
section to find the Stack trace tab with information on the stack trace itself.
You’ll notice the RuntimeException with the test message. Click the down
arrow to expand the stack trace fully.
The Data tab also provides more information about the crash, the device it
occurred on and the OS running on it. This can be useful for pinpointing bugs
specific to certain devices or operating systems or determining if there are any
potential memory leaks.
88
Android Debugging by Tutorials Chapter 4: Analyzing the Stack Trace
Great job on setting up Firebase Crashlytics! Now that you’ve got the crashes
reporting to the Crashlytics service, it’s time to try importing Crashlytics stack
traces into Android Studio to inspect them.
In the Stack trace tab, click the TXT tab to switch to the plain text view of the
stack trace. Copy the stack trace in its entirety.
89
Android Debugging by Tutorials Chapter 4: Analyzing the Stack Trace
A new tab in the Run window will appear with the stack trace provided. Text
formatting will apply and the section PodcastActivity.kt:84 will highlight as a
link.
Click the link, and it’ll take you to the line with your throw
RuntimeException() call.
Congrats! You’ve now mastered the art of reading and analyzing stack traces.
Challenge
It’s time to put what you’ve learned to use. There’s another bug that is present
in Podplay. Launch the app and search for any podcast. When the list of
90
Android Debugging by Tutorials Chapter 4: Analyzing the Stack Trace
podcasts displays, repeatedly tap one until the app crashes.
Use the skills you’ve learned in this chapter to find the stack trace and the
underlying bug, and fix it.
Key Points
A stack trace shows a list of methods called at a certain place in your code.
Understand the method calls that led up to the crash by reading a stack trace.
In the next chapter, you’ll learn how to manipulate data at runtime while
debugging your app. This will allow you to simulate certain situations and put
your app in a state that replicates crashes that your users will experience.
91
Android Debugging by Tutorials
A major part of debugging is interacting with and changing variables when your
code is in a suspended state. This allows you to manipulate responses to
simulate different scenarios and see how your app will react. The Variables
pane inside the Debug window allows you to perform this manipulation of
variables by providing useful tools, which you’ll be exploring in this chapter.
Make important variables more visible by using flagged and the Watched
pane.
Evaluate code at runtime.
Run the app in debug mode. Once PodPlay has loaded, tap the search icon,
enter the term “All about android” and open the All About Android (Audio)
podcast.
92
Android Debugging by Tutorials Chapter 5: Watches & Evaluating the Code
You’ll locate the Variables pane to the right of the Debug window:
Each line inside the Variables pane is a variable; these can be primitives or
objects. The first variable you see is this .
In Android, this refers to the scoped context. In this case, it’s the context of
EpisodeListAdapter . The Variables pane will always show the scoped context
as the first variable in the list. That way you can find out what context is
accessible from wherever your code is suspended.
There’s a chevron located to the left of this , which means that it’s an object
containing additional values. Click the chevron to expand this and see its
values.
Each variable in the pane provides the variable’s name and its value. If it’s an
object, it will display metadata to help you understand what type of object it is.
1. A chevron that you can click to expand the object and see the variables
inside.
93
Android Debugging by Tutorials Chapter 5: Watches & Evaluating the Code
In this case, the local scoped context ( this ) will always appear in the first row
of the Variables pane. The following variables in the list will be the ones that are
either method parameters or ones created within the current scope.
The following two variables in the list are holder and position . Both of them
have been passed into onBindViewHolder() as parameters.
Click Step over twice to execute the next two lines of code.
You’ll now see that two new variables have appeared in the Variables pane;
episodeViewList and episodeView . These local variables have just been
created in the two lines you just executed.
Drag and drop your breakpoint to your new line, so future suspends will occur
on this line:
The first and easiest way to make variables that you care about more visible is to
pin them to the top of an object. For now, Android Studio only allows you to pin
variables that are within an object and not in the top level of the Variables pane.
Do this for the String variable title . Find it within episodeView by clicking
the chevron to expand episodeView . Then scroll down and tap the variable
type identifier of title . It will turn into a flag icon when your mouse hovers
over it.
The first item within episodeView will now be title and it will stay there in
subsequent debugging sessions.
You’ve now also changed the variable type icon to a blue flag to indicate that it
94
Android Debugging by Tutorials Chapter 5: Watches & Evaluating the Code
has been pinned. To unpin a variable, simply click the flag icon again, and it will
go back to its ordered position.
For super important variables that you want to be able to monitor all the time,
you can add a variable to your Watches pane.
To show your Watches pane, click the glasses icon to the left of the Variables
pane:
Add title to your Watches by selecting the + icon inside the Watches pane.
Type in episodeView.title and then press Return:
95
Android Debugging by Tutorials Chapter 5: Watches & Evaluating the Code
Evaluating Code
Whenever your code is in a suspended state, you have access to retrieve and
manipulate everything available to your current scope. The most instantaneous
way to manipulate your code is with Evaluate Expression.
You’ll find Evaluate Expression at the end of the debugging actions toolbar:
Here, you can enter any code you like, and if your scoped suspended position
can evaluate the code, you can do that here too.
You can use the Evaluate button when debugging for many use cases; see below
for a few examples that you can try yourself in the current scope:
episodeView.description.contains("Android")
position > 10
DateUtils.dateToShortDate(episodeView.releaseDate!!)
If you want to see all of your previously evaluated expressions, use the small
96
Android Debugging by Tutorials Chapter 5: Watches & Evaluating the Code
arrow to the right of the Expression window.
Augmenting Code
Code augmentation is the act of changing the value of a variable to something
different from its real value. You can use this to simulate different data
responses and evaluate how your app UI responds.
There are a few different tools within Android Studio that you can utilize to
augment code. The easiest way to do this is to directly change the value of a
variable when your program is suspended.
position == 1
97
Android Debugging by Tutorials Chapter 5: Watches & Evaluating the Code
Click Done.
Your breakpoint will now only suspend the first time onBindViewHolder() is
called; this is when you’ll augment your code.
Build and run your app again in debug mode. Search for and click All About
Android (Audio) again, so your app suspends on your breakpoint.
You’re going to debug how your program behaves when there’s an unexpected
empty String in the variable mediaUrl .
You’re now viewing all of the podcast episodes in an ArrayList . Expand the
first item by clicking the chevron next to 0:
98
Android Debugging by Tutorials Chapter 5: Watches & Evaluating the Code
This is the podcast episode that you’re going to augment. Scroll down until you
see the variable mediaUrl , now right-click the variable and select Set Value…:
Replace the mediaUrl value with an empty text and press Return:
Now, click the Resume Program icon so that you can see how your program
responds.
With PodPlay running, select the first podcast episode in the list, the one that
you augmented.
Now try and play the podcast episode by clicking the Play icon.
This is bad as mediaUrl is a variable that an external API sends. You can’t
99
Android Debugging by Tutorials Chapter 5: Watches & Evaluating the Code
expect it always to be present. Your logic needs to handle edge cases where data
is empty like this.
Click the crash link for togglePlayPause to go to the method that threw the
IllegalArgumentException .
You can use Evaluate expression to quickly test a fix for this crash.
Remove the previous breakpoint and place a new one on the first line inside
togglePlayPause() .
Now build and run your app again in debug mode and go through all of the
previous debugging steps:
Your app should now be suspended inside togglePlayPause() right before the
crash occurs.
Click Evaluate Expression so you can see which code to use to capture this
scenario.
Try to find the correct code to use for checking if the mediaUrl text is empty by
using Evaluate Expression.
podcastViewModel.activeEpisodeViewData?.mediaUrl?.isEmpty()
You just saved yourself a lot of time running through the debug steps multiple
100
Android Debugging by Tutorials Chapter 5: Watches & Evaluating the Code
times to test different pieces of logic until you found the correct one.
Now you can add the isEmpty check at the first line of togglePlayPause() :
if (podcastViewModel.activeEpisodeViewData?.mediaUrl?.isEmpty() ==
true) {
Log.d("test", "MediaURL is empty, unable to play podcast
episode.")
Toast.makeText(context, "Unable to play podcast episode, media
URL missing.", Toast.LENGTH_SHORT)
return
}
Make sure you add the Log and Toast imports to the top of
EpisodePlayerFragment.kt:
import android.util.Log
import android.widget.Toast
This code will now check if the mediaUrl is empty. If it’s empty, it’ll display a
toast to the user and return from the method instead of crashing.
Go through the previous debug steps again to try out your new fix if you’d like!
Force Return
Changing the values of variables during code suspension is a powerful
debugging tool, but what if you wanted to augment the returning value of a
method? You can do this with Force Return.
Force Return allows you to end your current scope and return an evaluated
expression to the scope above it. Like direct code augmentation, it’s a great way
to test different scenarios of what a method might return.
Next, you’ll see how your UI behaves if a podcast doesn’t have a title or
description in this scenario.
101
Android Debugging by Tutorials Chapter 5: Watches & Evaluating the Code
Build and run your app again in debug mode, search for a podcast and select the
first one. Force Return lives in the Frames pane that you explored in a previous
chapter. In the Frames pane, you can see that the currently selected frame is
the method that you’re suspended in; podcastToPodcastView() :
A new window will now be visible called Return Value with an expression input
box. This is the place where you’ll return a new, augmented data value.
Select the expand icon next to the expression box, so you have more visibility of
the code you’re inputting:
Now, return a PodcastViewData object that has an empty title and description:
102
Android Debugging by Tutorials Chapter 5: Watches & Evaluating the Code
Note: Android Studio 2021.2.1 has an issue when pressing OK; the Return
Value window doesn’t close automatically. Clicking OK again will result in an
error that states “Error while doing early return: Thread has been resumed.”
To overcome this issue, press Cancel after pressing OK the first time.
Your frame will now have been returned, but your app is still suspended;
resume your app by clicking the Resume Program icon.
You have now successfully augmented your code with Force Return, you’ll now
see your podcast without a title and description within your UI. You’re also free
to leave it as it is or change how your app behaves when this data is missing.
Throwing Exceptions
You can also augment code by throwing exceptions. Throwing rogue exceptions
at your program is a great way to see how resilient your code is.
Within setState() you’ll see that halfway through the method, there’s a try-
catch statement that wraps around this code:
mediaPlayer.playbackParams =
mediaPlayer.playbackParams.setSpeed(speed)
103
Android Debugging by Tutorials Chapter 5: Watches & Evaluating the Code
If setSpeed() throws an exception, a set of logic is run. The only trouble is that
it’s hard to verify that the exception logic works if setSpeed() always works.
This is where you can augment the code to throw an exception.
Remove the last added breakpoint and set a new breakpoint on the line from the
previous code block:
104
Android Debugging by Tutorials Chapter 5: Watches & Evaluating the Code
RuntimeException("Boom!")
Click OK.
Click Step over once to move down into your catch scope, and you’ll see the
exception that you just created, with the message “Boom!” has been caught.
Click Resume Program, and your code will suspend again on the setSpeed()
method, which is the correct functionality in this instance, you can rest assured
knowing that your catch statement is behaving correctly.
Key Points
Use the Variables pane to observe your local and context-accessed variables.
Use Variable pinning and the Watches pane to keep important variables in
view.
You can use Evaluate Expression to debug your code logic without re-
running your app.
Use Force Return and Throw Exception to debug how your app behaves in
unusual scenarios.
105
Android Debugging by Tutorials Chapter 5: Watches & Evaluating the Code
pane to help you find and fix problems, but there’s still more to learn!
106
Android Debugging by Tutorials
6 Layout Inspector
Written by Zac Lippard
Up until now, you’ve learned many ways to debug your app’s code. But what if
you wanted to debug the UI? Enter, the Layout Inspector.
The Layout Inspector is a tool provided by Android Studio that enables you to
debug layouts in your app. The inspector takes a snapshot of the UI to provide
details about the layout. These details can be what views are in the UI hierarchy,
attributes of each view and a 3D rendering to inspect the views at different
angles.
Effective use of the Layout Inspector can help you catch potential defects in
your layouts, as well as help improve the overall UI performance.
In this chapter, you’ll use the Layout Inspector and learn how to:
107
Android Debugging by Tutorials Chapter 6: Layout Inspector
When you check this option, the Layout Inspector passes a flag to the launching
activity. The flag allows the inspector to connect to the app immediately.
Otherwise, the inspector has to restart the activity each time it’s loaded, and
you’ll have to navigate back to the view you want to inspect.
Run the app. When the app launches, start the Layout Inspector by clicking
Tools ▸ Layout Inspector from the Android Studio menu.
108
Android Debugging by Tutorials Chapter 6: Layout Inspector
1. Component Tree: This shows all the views in the hierarchy. You’ll notice this
particular layout’s hierarchy is shallow, with only two layers.
2. Layout Display: This is the rendered display of your layout. This section
allows you to zoom in on specific views in the layout and explore the layers of
the hierarchy in 3D.
3. View Attributes: When you select a view from the component tree or the
layout display, the attributes for that view appear here.
At the bottom right of the layout display section click the 3D Mode button.
Note: The first time you do this, it may ask you to install some additional
components within Android Studio.
Notice there’s a toolbar at the top of the layout display section. This toolbar
includes several features such as:
3. Exporting snapshots.
109
Android Debugging by Tutorials Chapter 6: Layout Inspector
The 3D rendering in the layout display allows you to observe the layout and its
layers, view outlines display and show you how many nested view groups are in
your layout. This rendering can help you determine if you have too many layers
and if your layouts are complex.
When the UI gets rendered, the system has to do a number of layout passes to
draw each view layer in the hierarchy. Too many nested layers can cause
drawing issues like overdraw and will slow down the layout process.
Overdraw occurs when the system attempts to draw over the same pixel or
group of pixels in layout passes. Overdraw is inefficient and often unnecessary
as the GPU has to re-process rendering and draw the same part of the screen
multiple times.
Note: To learn more about overdraw and ways to reduce it, read the Overdraw
Android developer documentation.
The layout display will show you potential overdraw areas within the 3D
rendering. Overlapping layers with background colors set in the same region
are candidates for overdraw. You should avoid this as much as possible.
Now that you’ve understood what the Layout Inspector can do, it’s time to
improve one layout in the Podplay app!
Go back to the Layout Inspector and click the Refresh layout icon.
110
Android Debugging by Tutorials Chapter 6: Layout Inspector
The layout display will refresh and show the hierarchy for the episode player
fragment.
Click the Live Updates icon in the inspector toolbar to use automatic updates.
Isolating a View
There’s a lot going on in the current layout display with all the views in the
hierarchy. What you’ll focus on is the podcastDetailsContainer view. Click that
view in the component tree, highlighting the associated view in the display.
111
Android Debugging by Tutorials Chapter 6: Layout Inspector
This is a bit better, but you can also isolate this container view and remove all
the others in the hierarchy that you don’t care about.
Now, the display section updates to only show the view hierarchy related to the
container:
Views in the component tree that aren’t a part of the selected subtree will grey
out with strikethrough text.
Click and drag the layers in the display section to rotate the 3D model. You can
also expand or contract the spacing by adjusting the Layout spacing slider.
Increase the spacing to provide more clarity in the subtree display:
112
Android Debugging by Tutorials Chapter 6: Layout Inspector
Take note of the nested layers in the highlighted section in the layer display
above. You’ll work on condensing this layout to remove some extra layers.
Before you start, take a picture of the app in this state. You’ll use this picture
later to confirm that your changes align with how the app currently looks.
To take a screen capture, click the Logcat tab in Android Studio and click the
Screen capture icon.
113
Android Debugging by Tutorials Chapter 6: Layout Inspector
<RelativeLayout
android:id="@+id/headerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#eeeeee"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/episodeImageView"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:contentDescription="@string/episode_thumbnail"
android:src="@android:drawable/ic_menu_report_image" />
<TextView
android:id="@+id/episodeTitleTextView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_alignBottom="@+id/episodeImageView"
android:layout_alignTop="@+id/episodeImageView"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_toEndOf="@+id/episodeImageView"
android:text="" />
</RelativeLayout>
<ImageView
android:id="@+id/episodeImageView"
android:layout_width="68dp"
android:layout_height="68dp"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:contentDescription="@string/episode_thumbnail"
android:src="@android:drawable/ic_menu_report_image"
android:background="#eeeeee"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
114
Android Debugging by Tutorials Chapter 6: Layout Inspector
android:id="@+id/episodeTitleTextView"
android:layout_width="0dp"
android:layout_height="0dp"
android:paddingEnd="8dp"
android:paddingStart="8dp"
android:text=""
android:gravity="center_vertical"
android:background="#eeeeee"
app:layout_constraintTop_toTopOf="@id/episodeImageView"
app:layout_constraintStart_toEndOf="@id/episodeImageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@id/episodeImageView" />
If you try to compile the app it’ll fail because we’ve removed the headerView
element which EpisodePlayerFragment references. Rather than changing the
code to change the visibility of each child view, you can use a Group . Add the
following below episodeTitleTextView :
<androidx.constraintlayout.widget.Group
android:id="@+id/headerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="episodeImageView,episodeTitleTextVie
w" />
The next view group you’ll condense is the playerControls view. This is a fairly
complex LinearLayout which contains the controls for the player, such as
rewind, play/pause and fast-forward. This is within a nested RelativeLayout .
There’s also another nested LinearLayout which holds the time controls, like
the start and end times, as well as the seek bar.
115
Android Debugging by Tutorials Chapter 6: Layout Inspector
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/playerControls"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/background_dark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
Next, you’ll need to add the nested child views as a single, flattened layer.
Replace nested RelativeLayout and LinearLayout views with the following
code:
<Button
android:id="@+id/playToggleButton"
android:layout_width="34dp"
android:layout_height="34dp"
android:layout_marginTop="8dp"
android:background="@drawable/ic_play_pause_toggle"
android:scaleType="fitCenter"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/seekBar" />
<ImageButton
android:id="@+id/forwardButton"
android:layout_width="34dp"
android:layout_height="34dp"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:background="@android:color/transparent"
android:contentDescription="@string/skip_forward"
android:scaleType="fitCenter"
android:src="@drawable/ic_forward_30_white"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/playToggleButton"
app:layout_constraintBottom_toTopOf="@id/seekBar" />
<ImageButton
android:id="@+id/replayButton"
android:layout_width="34dp"
android:layout_height="34dp"
android:layout_marginEnd="24dp"
android:layout_marginTop="8dp"
android:background="@android:color/transparent"
android:contentDescription="@string/replay_button"
android:scaleType="fitCenter"
android:src="@drawable/ic_replay_10_white"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/playToggleButton"
app:layout_constraintBottom_toTopOf="@id/seekBar" />
<Button
116
Android Debugging by Tutorials Chapter 6: Layout Inspector
android:id="@+id/speedButton"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="5dp"
android:background="@android:color/transparent"
android:text="@string/_1x"
android:textAllCaps="false"
android:textColor="@android:color/white"
android:textSize="14sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/seekBar" />
<TextView
android:id="@+id/currentTimeTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginStart="8dp"
android:text="@string/_0_00"
android:textColor="@android:color/white"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<SeekBar
android:id="@+id/seekBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:progressBackgroundTint="@android:color/white"
app:layout_constraintStart_toEndOf="@id/currentTimeTextView"
app:layout_constraintEnd_toStartOf="@id/endTimeTextView"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/endTimeTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:text="@string/_0_00"
android:textColor="@android:color/white"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
All the views constrain to either the parent view or each other. No more nested
RelativeLayout or LinearLayout required!
Build and run the app, search “android” again and select the same podcast and
episode. You’ll notice that the episode player layout looks the same but with
fewer layers. You can also confirm that the layers have been condensed down by
using the 3D mode in the layout display:
117
Android Debugging by Tutorials Chapter 6: Layout Inspector
Don’t believe your eyes that view layers are the same? Well, next, you’ll confirm
that they actually do match! :]
Select the Logcat screen capture that you saved from earlier. The inspector’s
display section updates and includes the overlaid image on top of the rendered
layout.
118
Android Debugging by Tutorials Chapter 6: Layout Inspector
1. Use the Overlay Alpha slider to change the overlay’s opacity. As you adjust
the slider, the image fades in and out. There won’t be any changes between
the overlay image and the rendering from the Layout Inspector. With this
tool, you can officially confirm that the views remained the same while
condensing the hierarchy.
2. Finally, click the Clear Overlay button in the toolbar.
To export an inspector snapshot, click the Export Snapshot icon in the toolbar:
When you’re ready to view the snapshot, you can open it directly in Android
Studio via File ▸ Open and select the .li file.
119
Android Debugging by Tutorials Chapter 6: Layout Inspector
The view for this file is the same as the Layout Inspector window, and you can
perform all the same tasks on this screen as you can in the inspector window.
Validating Layouts
Before running your application, you can visually inspect your changes in the
XML layout files with the Layout Validation tool. This tool helps you confirm
that the layouts you write look correct across a wide range of device references,
color settings and font sizes.
120
Android Debugging by Tutorials Chapter 6: Layout Inspector
121
Android Debugging by Tutorials Chapter 6: Layout Inspector
This window shows you what the layout will look like under different device
configurations.
1. Configuration Set: Changes the device configuration itself. This is where you
can choose different device types and do colorblind tests or font size
validation.
2. View Options: Provides options to change the layout. The main option is
Show System UI, which you can enable now. This particular option will
display the status and action bars at the top and the hardware buttons at the
bottom.
Devices Types
As shown above, the default configuration setting is the Reference Devices.
Using this configuration, the Layout Validation shows the layout on a range of
device types, including phones, foldables, tablets and even desktop
122
Android Debugging by Tutorials Chapter 6: Layout Inspector
configurations. This saves you time from having to load multiple emulators for
each device type or gather actual devices and run the app on each one.
You can also show Wear OS configurations if you’re working on a wearable app.
While the episode player view isn’t intended for this use, you can still check out
how the layout will appear on the different Wear OS configurations.
Here, you can set your configuration settings. For this example, give it the name
“Landscape” and choose LANDSCAPE for the orientation.
123
Android Debugging by Tutorials Chapter 6: Layout Inspector
Click Add, and the validation window will update to include the layout using
your custom configuration:
Colorblind Tests
It’s important to ensure your layouts are accessible to as many people as
possible. In the Layout Validation tab, you can perform a layout test to confirm
that your app is still accessible to users who are color blind.
124
Android Debugging by Tutorials Chapter 6: Layout Inspector
Multiple layouts appear showing the different types of common color blindness:
Protanopes and Protanomaly represent how people with severe and mild
red-green color blindness will see the layouts. Defects in the red cone eye
pigments of the eye cause this type of color blindness.
Tritanopes represents the layouts in the way users with blue-yellow color
blindness see them.
These different views help you determine if there are color clashes that you’ll
need to address before releasing your app to the public.
Font Sizes
Along with the topic of accessibility, font sizing is another essential aspect to
test. A common way of testing your app is by adjusting the font size via the
Settings app and then opening your app and verifying that the changed font size
looks good.
With the Font Sizes configuration, you can easily view at once how your layout
will appear with different font sizes.
125
Android Debugging by Tutorials Chapter 6: Layout Inspector
The default font size at 100% is displayed, along with small — 85%, large — 115%
and largest — 130%. This allows you to see font size differences side-by-side.
You can also determine if there’ll be any issues with your layout if a user needs
to use an increased font on their device.
Challenge
Congratulations on condensing down those views in the episode player! But,
there’s still more you can do. You converted the playersControl view to a
ConstraintLayout in fragment_episode_player.xml. Improve that layout
again and remove the playerControls layer altogether. After removing the layer,
move its children into the root ConstraintLayout . Afterward, verify that the
layer is gone by using the Layout Inspector and its 3D rendering mode.
126
Android Debugging by Tutorials Chapter 6: Layout Inspector
Key Points
The Layout Inspector displays the views as layers in the UI hierarchy.
The inspector’s 3D render tool allows you to view the layers.
Add overlays in the inspector to confirm changes are accurate.
Validate layouts against device types, color blindness tests, and font sizes.
The journey isn’t over yet! In the next chapter, you’ll learn how to live query
databases with the Database Inspector.
127
Android Debugging by Tutorials
7 Debugging Databases
Written by Zahidur Rahman Faisal
The word Data originates from the word datum which means a single piece of
information. Data is the plural for datum.
Basically, data is a distinct, small unit of information that can be used in various
forms like text, numbers, media or bytes etc. It can be stored in electronic
memory or pieces of paper just like in a book!
In the previous chapter, you learned about debugging views and optimizing
layouts to display details of the podcast channel that you’ve subscribed. At this
point, you must be wondering if you could debug the way podcasts are stored in
PodPlay.
In this chapter, you’ll check how your subscribed podcasts are stored and
organized in your database, and you’ll resolve the episode ordering issue.
It wouldn’t be easy for you to find a book in a library if the books weren’t
organized. You might get lost searching for a book that’s recently been added if
someone just put the book randomly on any shelf. You might have faced the
same situation in the PodPlay app! When using the app, you access a vast
library of podcasts and can subscribe to your favorite ones to come back to
later. The details of your subscribed podcast channels are stored locally in the
app, just like a library. PodPlay uses a Room Database to store your
subscriptions.
128
Android Debugging by Tutorials Chapter 7: Debugging Databases
To start, open the Podplay starter project and run the app. Tap the search icon
in PodPlay, type “RW” in the search bar and press Return. Choose any podcast
and tap it to open its details. Finally, tap SUBSCRIBE to subscribe to the
podcast.
You’ll now see the channel name in the subscribed channel list. Tap it to open a
detailed screen, and you’ll see a list of episodes underneath the title and
description of the channel. If you look closely, you may notice the episodes
aren’t shown in the correct date order; you’d expect to see the recent episode on
top of the list and then the older ones in chronological order.
129
Android Debugging by Tutorials Chapter 7: Debugging Databases
Database Inspector. To open the Database Inspector in Android Studio, you need
to:
2. Select View ▸ Tool Windows ▸ App Inspection from the menu bar.
130
Android Debugging by Tutorials Chapter 7: Debugging Databases
Previewing Database
You’ll see a list of databases in your app and the tables each database contains
within the Databases pane. The name of the database in PodPlay is PodPlayer.
131
Android Debugging by Tutorials Chapter 7: Debugging Databases
Simply double-click on the Podcast table to display its data in the inspector
window.
Looking at this, you can verify if you’ve defined the proper data types for your
fields. This is useful to tackle issues where any unsupported data might have
accidentally been inserted into a specific field in your table.
1. To find your latest added podcast, click the lastUpdated column. It’s the
timestamp of when you subscribed to the channel. Clicking on a column
header sorts the data in the inspector window by that column, so it’ll display
the rows in a chronological order based on your timestamp.
2. Double-click on the first item under the feedTitle column, then change
the title to “My top favorite channel” and press Return.
132
Android Debugging by Tutorials Chapter 7: Debugging Databases
PodPlay uses Room Database, and the UI observes the database with LiveData,
so your update will be instantly visible in your running app. Otherwise, the
changes would only be visible after you launch the app next time.
This is because you need to reload the contents of the current table that’s visible
to you in the Database Inspector.
Now, click the highlighted icon in the above image. This will refresh the
contents, and the channel you just subscribed to will show up in the Podcast
table!
It’s convenient to see live updates while you interact with a running app; to
enable that feature you need to click the Live updates checkbox beside the
Refresh icon.
Enable the Live Updates feature and subscribe to something. The subscribed
channel is immediately shown in your Podcast table, as the name suggests.
Note that the table in the inspector window becomes read-only, and you can’t
modify its values with Live updates enabled. So, trying to double-click and
change a value will have no effect while in Live updates mode.
133
Android Debugging by Tutorials Chapter 7: Debugging Databases
correctly, the next step is to check if your database queries are correct.
The Database Inspector can run queries on your app’s database while you’re
running the app. The tool can easily execute DAO queries that you’ve written for
your Room database, but it also allows you to write custom SQL queries to
validate your ideas!
Looking at the queries here, it’s obvious that loadEpisodes() is responsible for
providing a list of episodes to your UI layer.
You can easily find out what this query returns by clicking on the button next to
@Query like below:
Upon clicking, a prompt appears for you to select the database to execute the
query on. Select PodPlayer from the drop-down list.
134
Android Debugging by Tutorials Chapter 7: Debugging Databases
Next, there will be another popup showing the query you choose to execute and
asking for a value for your query parameter, if any, which is podcastId .
Input “1” to load episodes from your first subscribed podcast channel.
If your query method includes more than one parameter, Android Studio
requests values for each parameter before running the query.
You’ll see the results of the query in a new tab within the Database Inspector like
below:
1. Open the Open New Query tab at the top of the Databases pane.
135
Android Debugging by Tutorials Chapter 7: Debugging Databases
2. Select the database PodPlayer to query from the drop-down list on the New
Query tab.
3. Type the following SQL query into the text field at the top of the New Query
tab: “SELECT * FROM Episode WHERE podcastId = ‘1’ ORDER BY releaseDate
DESC”
Here’s another simple approach to doing this since your query hasn’t changed
much apart from a keyword:
1. Click the Show query history icon beside the database name in your custom
query pane to see a list of queries that you previously ran.
2. Click a query in the list to see a preview of the full query in the editor, and
press Return to copy it to the editor.
3. Modify the query part you want. In this case, change the order from ASC to
DESC in your query.
136
Android Debugging by Tutorials Chapter 7: Debugging Databases
4. Click Run to execute the statement.
Look at the releaseDate column now; the items are in descending order. That
means the custom query will return the latest episode first and the others
subsequently, which solves your issue!
You can limit the number of items on a page for simplicity by clicking on the
highlighted area.
Now you’re certain the new query works and it will deliver your expected
results, open PodcastDao.kt and update the @Query above loadEpisodes()
like below:
Select a database in the Database pane, then click ‘Export as File…’ near the
top-left corner to export the whole database.
137
Android Debugging by Tutorials Chapter 7: Debugging Databases
Click ‘Export as File…’ query results while viewing a table or query results in
a tab.
To complete the export action, you need to choose a format for the exported
file. Depending on whether you’re trying to export a database, table, or query
results, the Database Inspector will popup three options for exporting the data
in the following formats:
1. DB: Preferable format if you’re going to import or use the database in mobile
applications.
2. SQL: Easy to open or modify the exported file on any desktop or web-based
database system.
3. CSV: Simple, lightweight and widely accepted format, good for batch
reading/data writing.
If you select CSV format, you can also choose how the values are separated in a
row from the drop-down below:
Then the Database Inspector will accumulate the tables from the PodPlayer
database in different .csv files and create a single .zip on your selected path to
export.
138
Android Debugging by Tutorials Chapter 7: Debugging Databases
This feature can save your day in an emergency by simplifying the backup and
sharing process. You can quickly restore your entire database with a few clicks!
Key Points
You can look into your existing database easily with Database Inspector.
Check your database schema carefully and validate your assumptions about
updating fields.
Using the Live updates feature will save you time to observe changes, but you
can’t modify data in this mode.
Save time running your queries directly from the Android Studio DAO.
Copy and modify a query or write a custom one to see quick results.
Offline mode
If you’re interested in mastering the ways of storing data on Android, these are
the best starting point for you:
At this point, you’re probably curious to know how fetching and saving data
works in the background! In the next chapter, you’ll learn to inspect on
background tasks using Background Task Inspector, so stay tuned!
139
Android Debugging by Tutorials
8 Debugging WorkManager
Jobs With Background Task
Inspector
Written by Zahidur Rahman Faisal
In the previous chapter, you found out how to use the Database Inspector to
investigate your database and the Live Updates feature to observe data changes.
You learned how to use a query for previewing results while debugging the app
and you got familiar with exporting your database to make sharing easier.
All this sounds really great, but you’re probably wondering where to find the
data for populating your local database and how to download it. That could
become a demanding task if the data is big and you need to sync it often. You
need to think about user experience as well. Therefore, the solution is to run
complex tasks in the background.
Here are the conditions one task needs to meet to become a background task:
The task isn’t running any foreground services that the user explicitly started.
2. Long-Running: Tasks that run for a more extended period, usually more
than 10 minutes, e.g. downloading big files.
140
Chapter 8: Debugging WorkManager Jobs
Android Debugging by Tutorials With Background Task Inspector
PodPlay relies on Android WorkManager to handle all types of background
tasks. The app is smart enough to regularly check your subscribed channels
and update the episode list if new episodes are published. This job runs in the
background without you even realizing it’s happening!
This is great, but it can take a toll on your mobile data and CPU usage if the
background operation is frequently running. In this chapter, you’ll inspect
background tasks and schedule them to use optimal resources. You’ll learn how
to:
1. Open the Podplay starter project and run the app on an emulator or
connected device using API level 26 or higher.
2. Select View ▸ Tool Windows ▸ App Inspection from the menu bar.
141
Chapter 8: Debugging WorkManager Jobs
Android Debugging by Tutorials With Background Task Inspector
menu.
142
Chapter 8: Debugging WorkManager Jobs
Android Debugging by Tutorials With Background Task Inspector
1. The class name that implements the Worker interface handling the work.
2. What state that work is in right now (e.g., Enqueued, Running, Blocked,
Succeeded or Failed).
3. The exact time that the work started execution.
4. How many times the work has been retried if there was any problem during
execution.
The table displays the list of workers in alphabetical order based on their class
name by default, but you can sort them based on each field above. For example,
click the Start column, and that task will be sorted by its start time, as you can
see below:
Your next objective is to leverage them and find details about a specific worker
from the WorkManager ’s queue.
143
Chapter 8: Debugging WorkManager Jobs
Android Debugging by Tutorials With Background Task Inspector
dropdown.
Anatomy of a Worker
Now that you’ve filtered GetNewEpisodesWorker from the table, click the row to
reveal details about the work. The Task Details panel will open next to the row.
The panel breaks down the information into four major segments:
Class: Displays the worker class name and the fully-qualified package name.
Tags: The default tag assigned by WorkManager or any additional tags
specified by the developer to identify this worker while creating it.
144
Chapter 8: Debugging WorkManager Jobs
Android Debugging by Tutorials With Background Task Inspector
Enqueued By: Shows the class name which created and enqueued this
worker. The class name is only shown if that worker is enqueued after
opening the table; otherwise, it shows as “Unavailable”.
Constraints: Displays details if the worker is running under any work
Constraints. Constraints are one or more rules that must be met before
executing the work as pre-conditions, for example, internet connectivity. If
no constraints are set, then it simply displays “None”.
Frequency: Indicator that represents how many times the worker’s going to
execute the work. It can be a OneTimeWorkRequest or PeriodicWorkRequest
that’s scheduled and repetitive.
State: The work’s current state, either it’s Enqueued, Running, Blocked,
Succeeded or Failed.
Next: UUID of the next worker in this work chain (if available). It displays
“None” if no worker is queued after this.
Unique work chain: This is a quick overview of your work chain. It lists the
UUIDs of all the workers in the sequence that they are executed. The worker
marked as “Current” is the worker you’re viewing details of right now. The
tick icons beside each UUID mark that the task has been executed
successfully.
4. Results - This section is for displaying the final results of executed work.
145
Chapter 8: Debugging WorkManager Jobs
Android Debugging by Tutorials With Background Task Inspector
Retries: Shows the number of times the work has been executed, which is
one, in this case. If work failed for some reason, the retry count would have
increased.
Output data: The returned result or data from doWork() in your worker
class.
Click the first UUID from the Unique work chain to preview its details. The
Task Details panel changes, as you can see below:
146
Chapter 8: Debugging WorkManager Jobs
Android Debugging by Tutorials With Background Task Inspector
Now the Task Details panel show for your first worker LoadPodcastsWorker ,
which means you’re pointing at the beginning of your work chain.
Next, click on the last UUID in Unique work chain; it will navigate you to the
end of the work chain:
Look at the Task Details panel again; it shows the worker class is
147
Chapter 8: Debugging WorkManager Jobs
Android Debugging by Tutorials With Background Task Inspector
The graph presents each work from the execution flow, maintaining the exact
order. So, the graphical representation of your work chain looks like this:
It’s visible from the graph that there are three workers in the work chain:
Click on of the highlighted points below to easily switch back to the table view:
148
Chapter 8: Debugging WorkManager Jobs
Android Debugging by Tutorials With Background Task Inspector
149
Chapter 8: Debugging WorkManager Jobs
Android Debugging by Tutorials With Background Task Inspector
1. Loads PodcastRepo , the repository where all your bookmarked podcast
channels are stored.
2. Calls updatePodcastEpisodes() that fetches the latest episode list from the
channels and returns updates as a list in podcastUpdates .
Your next step is to schedule this job using WorkManager . To do so, open
PodcastActivity.kt and replace scheduleJobs() as follows:
WorkManager.getInstance(this).enqueueUniquePeriodicWork(TAG_EPISODE
_UPDATE_JOB,
ExistingPeriodicWorkPolicy.REPLACE, request)
}
1. Defines a work constraint to run the job only when the device is connected to
the internet and the battery isn’t low. This ensures optimum resource
consumption when the worker is running or prevents the worker from
running if the conditions aren’t met.
2. Creates a PeriodicWorkRequest that’s scheduled to execute once every hour
if the work constraints are satisfied.
Now, build and run the app and observe the work from Background Task
Inspector; you’ll see some interesting developments!
150
Chapter 8: Debugging WorkManager Jobs
Android Debugging by Tutorials With Background Task Inspector
The changes you see on the highlighted points are listed below:
4. The Results section is showing “Awaiting data…” for output because the work
is scheduled to be executed in the future.
Canceling a Worker
Sometimes you may want to cancel a worker which is scheduled to run in
future. It’s easy to stop a currently running or enqueued worker using
Background Task Inspector. Select EpisodeUpdateWorker and click the
highlighted icon from the toolbar:
151
Chapter 8: Debugging WorkManager Jobs
Android Debugging by Tutorials With Background Task Inspector
This will suspend any pending or scheduled work; you’ll see the change
immediately in the Status column.
Next, you’ll learn how to monitor network activity with Network Profiler while
you add new episodes in PodPlay. Good luck on your next quest!
Key Points
You can easily inspect WorkManager workers using Background Task
Inspector.
The Task Details panel reveals the current state, execution flow, work
continuation and results of background work.
You can see a detailed work chain graph using Graph View.
But don’t stop there; take a look at our tutorials related to the background
processing:
152
Android Debugging by Tutorials
Efficiently using the CPU is critical when it comes to your Android app. It
determines the difference between jittery and smooth UI and significantly
impacts a device’s battery life.
It’s important to keep in mind your app’s impact on resources. Your app should
run smoothly and feel fluid throughout. A choppy app won’t bring delight to
your end-user.
Your app should also be efficient and not have unfettered access to system
resources. Extensive use of system resources causes other applications to delay
their operations. This also drains the battery and could lower the app’s overall
performance.
This chapter covers the Android Studio CPU Profiler and how you can use this
tool to track CPU usage.
You’ll learn:
All about CPU traces including system traces and method/function traces.
Recording traces.
Inspect traces using the many different charting formats available in the CPU
Profiler.
To start, open the Podplay starter project in Android Studio and run the
application. Once the app is running, you can connect the Profiler to your app’s
process. To do so, click View ▸ Tool Windows ▸ Profiler, or simply click the
Profiler tab in the bottom toolbar.
153
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
In the Profiler window, you’ll notice there’s not really much happening there
yet. You now need to connect the Profiler to your running Podplay app process.
1. Click the + icon in the top-left of the Profiler window in the Sessions toolbar.
You’ll notice a new entry on the left in the Sessions column. On the right, the
Profiler has started collecting data. The session will remain until you close
Android Studio. There are sections for CPU, Memory, Networking, and Energy.
For the purposes of this chapter, you’ll focus on the CPU section.
Double-click the CPU section, and the Profiler will update to show the CPU
panel.
To the left of the CPU panel is a section to create trace recordings and
configurations.
154
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
Each timeline section provides unique details about how your app is interacting
with the CPU:
2. CPU timeline: This timeline displays the CPU utilization as a percentage. The
timeline will also show CPU utilization belonging to other processes to
compare your app against.
3. Thread activity timeline: Active threads that are running as part of the
Podplay application process. A green section indicates the thread is active
and running, or at least in a runnable state. Yellow represents that the
thread is active but waiting for an IO operation. Gray shows threads that are
sleeping and not consuming any CPU.
155
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
First, ensure Android Studio isn’t running the profiler, or close Android Studio
altogether. Next, open a terminal window to the bin folder under the Android
Studio installation:
Windows/Linux: <studio-installation-folder>/bin
macOS: <studio-installation-folder>/Contents/bin
A new Android Studio profiler dialog will appear where you can start new
sessions, same as listed above via the Profiler window.
One of the critical components of the CPU Profiler is the ability to record traces.
However, before diving into recording and configuring traces, you’ll learn what
traces are.
Traces
A trace is a set of data that details how a process and its threads interact with the
CPU and other resources. Typically the trace data is extremely thorough and
covers method/function calls, rendering data, lifecycle calls on the Activity or
Fragment and user input. All of these combined helps to paint a picture of how
your app performs and what resources it’s using.
System Traces: Details how your app as a whole interacts with resources on
the given device.
156
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
You can use trace data to see where your app may be utilizing more resources
than necessary.
Now that you know what traces are, it’s time to record one!
Recording Traces
Run the Podplay project in Android Studio. Start a new CPU Profiler session by
clicking on the Profiler window tab at the bottom of Android Studio. Click the +
icon and select the device and Podplay process to launch the new session. Now,
click the CPU timeline to open the CPU Panel.
157
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
timestamps to the beginning and end of each method in the call stack. These
timestamps are then used and aggregated to calculate app timing as well as
CPU utilization. Note that this configuration adds significant overhead due to
the required instrumentation. For short-lived methods, this configuration
could result in inaccurate time data. If your app makes a large number of
method calls throughout the recording, it could cause the Profiler to exceed
its maximum file size limit.
Recording a Trace
To record a trace from the CPU Panel, click Record.
You’ll notice that once the recording starts, the CPU timeline background
changes color, and there’s an “In progress” tooltip in the timeline at the start of
the recording.
Now that you’ve collected some data in the recording, click Stop.
158
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
The recording entry includes the configuration type and a timestamp of when
the recording occurred during the profiler session.
First, click Select Run/Debug Configuration at the top of Android Studio. Click
Edit Configurations….
Click the Profiling tab. Here you can check the Start this recording on startup
option. Select the CPU activity option and choose one of the record
configurations.
159
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
Click OK to save the run configuration and close the dialog. Then, in Android
Studio, choose the Run ▸ Profile… menu option, or click on the Profile ‘app’
icon:
Notice that the Profiler immediately starts when the app launches. A new
session will appear in the left column along with the recorded trace during the
app’s run.
160
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
Exporting a Recording
Now that you’ve acquired a number of recorded traces, it’s time to export them.
Hover over one of the recordings, and you’ll notice a save icon that you can
click.
Click the save icon, then in the new dialog, click Save. Your trace recording is
now saved in a .trace file.
Importing a Recording
You can also import recordings to Android Studio. This is useful if you want to
share and access a recording from another workstation or with other
developers.
To import a recording, click the Profiler tab to open the profiler window.
Select the recording that you exported in the previous section. A new session
will appear with the imported trace recording:
The trace filename and the associated recording configuration type appear in
the session entry.
161
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
Begin by running Podplay in Android Studio. Once the recording has started,
tap Search. Search for “android”. Once the podcasts load, it’s time to start the
profiler session.
Start a new profiler session and record a new trace using the Java/Kotlin
Method Trace Recording configuration. Once the recording starts, go back to
the app and tap on a podcast. When the podcast details page loads, go back to
the profiler window in Android Studio and click Stop to end the recording.
Now that you have a trace, it’s time to use the different charts to inspect it!
Call Chart
The first charting option is the Call Chart. It appears under the Threads section
of the trace. This chart provides a visual aid of the method and function calls
made during the timeframe of the recording.
162
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
1. The CPU Usage timeline shows an overall CPU utilization as a timeline. You
can drag the vertical blue start and end lines to focus on a specific portion of
the timeline. Doing so adjusts the associated data and will update the details
in the other places like the Threads section.
2. Select the main thread and expand it by clicking the arrow. Notice that the
call chart updates and reflects all the methods and native functions called by
the app during the time window you selected. There are three different
colors used to represent the different method calls in the chart:
3. The method calls in the Threads section display from top to bottom, where
the topmost method is the first call, and the subsequent method below is the
next call in the list. In this example, there are system calls at the top that
launch a coroutine that eventually fires
PodcastActivity$onShowDetails$1.invokeSuspend() . This method then
calls the next method in the list PodcastViewModel.getPodcast() . The call
list continues down from there.
4. Notice how the method lengths get shorter the lower down the list you go?
163
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
You can think of methods below a certain method in the list as “children”.
Each child runs as long or as short as the parent. In the example above, the
child method getEndIdx() runs for a fraction of the time it’s parent caller
Formatter.parse() ran. These methods are all descendants of
PodcastViewModel.getPodcast() .
The call chart in the Threads section is extremely valuable for determining what
methods are taking longer than expected. You can drill down deeper to see if
there are unnecessary calls to other methods or if there’s a child method that’s
an underlying culprit to poor performance in the app.
3. The call stack will display somewhat expanded. You can continue to expand
them by clicking the arrows on each method to see its children. In the
screenshot, loadPodcast() expands along with it’s child execute() . The
nested execute() then has a series of methods it calls. In the top down
chart, child methods appear below their parents.
4. The nested execute() has a series of child methods within it. Notice that
execute() , along with its ancestor methods, took 48,244 microseconds
(~48 milliseconds) to run. The Total (μs) column represents the total
amount of time the method took to execute code within itself and all the time
its children took. The child invokeOnCancellation() took a total of 3,373
microseconds.
164
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
execution.
The granular breakdown of this chart provides you with a view of how long each
method and child are taking. You can use this data to better understand how
long methods are taking and begin to pinpoint why they run as long as they do.
The colors for the chart are slightly different compared to the Call Chart, but
the same method types are represented here:
165
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
Dark orange represents the third part API method calls.
Light orange shows methods that belong to the app.
In this chart, you’ll see all the methods called over the period of the recording at
the top level. The difference though is that ancestor methods will have less
associated with them.
The same columns are here in this chart as in the Top Down Chart to represent
total/self/child time and percentage.
Note: With any charts under the Analysis section, you can use the search box
to bold methods that fit the search criteria. This is useful when you have
many methods that you’re trying to look through.
166
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
Events
The Events tab is useful to see all the calls for the thread selected in the
Threads timeline.
Click an event to update the Call Chart in the Threads timeline to reflect the
associated methods tied to the event.
You’ve taken a deeper dive into method and function tracing, but now it’s time
to see the power behind system traces and what insights they provide into
system resources.
Firstly, record a trace, but this time with the System Trace Recording
configuration. You can follow the same steps as you did earlier by running the
app and searching for “android” podcasts. Once the list of podcasts displays,
stop the recording.
167
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
UI Interaction
The Interaction timeline shows you what the user did during the time frame
captured.
Action data recorded here are things like taps and text input. Lifecycle-related
information for activities and fragments is also displayed here. You can hover
over a specific interaction to get more details about the event’s length.
The Display timeline shows information about drawn frames and what is set to
draw next from the buffer.
You can hover over the data in the timeline to see the values for each of these.
The Frame Lifecycle timeline shows the lifecycle of a particular “frame” that
168
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
displays to the user. Activity and Fragment instances build the screen
associated with a given frame. In this example, the frame in the timeline
associates with PodcastActivity .
Application: The duration of the app drawing the frame to the screen.
Wait for GPU: The time required for the GPU to complete the drawing.
Composition: The composition time taken by the SurfaceFlinger.
UI Jank
Jank refers to an unpleasant or unstable experience within an app. Specific to
the user experience, UI jank is often referring to jittery UI that feels unnatural
or jarring to the end-user.
Aside from visually inspecting the running app, the most common way to
determine jank is to look for skipped frames in Logcat.
For devices running Android 12+, there is also a way to detect these janky
frames from a system trace:
1. When there is jitter in the UI, the Janky frames section will appear in the
Display timeline of a system trace.
2. Click a frame in the section to view details about the jank type as well as
expected and actual durations it took for the frame to render.
169
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
CPU Cores
The CPU Cores timeline gives insight into how the work your app requires
allocates to the device’s CPU. This section lists each CPU core and frequency.
You can hover over a particular thread on one of the cores to get more
information. Some of these details include how long each thread is taking and
the associated process.
This timeline breaks down the memory usage between shared and non-shared
memory requirements. Non-shared memory is specifically allocated to the app
for normal and file mapping purposes.
In Android 10 and higher, system trace files save in the Perfetto format. This
format includes more system data sources than Systrace does. You can record
much longer traces with the Perfetto format. Along with the format, Perfetto is
also the platform-wide tracing tool. Android, Linux and even Chrome all use
this format. You can also use this tool via the command line with perfetto .
Systrace is a legacy system trace tool and format used in Android 4.3 and higher.
Systrace files save as compressed text files. Thus, they cannot contain as much
170
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
Perfetto UI is an online tool that you can use to parse and inspect Perfetto and
Systrace formatted system traces.
To begin, take one of the system traces you recorded earlier in this chapter and
go to Perfetto UI.
In the left column, click Open trace file and open either your Perfetto or
Systrace formatted trace file.
Once you’ve selected your file, Perfetto UI will update and display the timeline.
171
Android Debugging by Tutorials Chapter 9: Profile CPU Activity
Challenge
Remember the topic about UI jank? Well, it turns out there’s some jankiness in
this chapter’s starter and final Podplay app as well. Open the Logcat window and
run the Podplay app. Tap Search and search for “android”. When the list of
podcasts appears, tap any. Then, tap on a podcast episode to launch the player.
You’ll notice a delay before the loading spinner appears. During this time, you’ll
see logs stating Skipped X frames! .
Using the knowledge you gained from this chapter, record and inspect method
or system traces to fix the UI jank and make it run smoothly!
Note: The jank specific to this challenge is only in this chapter’s starter and
final Podplay projects. It isn’t present in any of the other chapter’s starter and
final projects.
Key Points
System traces measure performance on system resources and the UI.
Check out the Android developer CPU Profiler doc for more details on what the
Android Studio CPU Profiler has to offer. Interested in learning more about the
types of system tracing tools at your disposal? Check out this system tracing
Android developer doc.
Now that you’ve learned about the CPU aspect of the Profiler, it’s time to take a
look at how you can use the Profiler for memory usage.
172
Android Debugging by Tutorials
Memory is a storage space in computers or mobile devices, just like the human
brain! This is a place where data or programs are kept on a temporary or
permanent basis to be processed.
Any smartphone app like PodPlay keeps occupying your device’s memory as
long as it’s running in the foreground or background. It’s important to ensure
your app is functional with minimal memory usage and leaves enough room for
the Android OS and other apps to operate correctly. The Memory Profiler is a
tool built within Android Studio that helps you understand, analyze and
optimize your app’s memory usage.
Before moving any further, you might want to recall Android’s memory
management basics.
Memory Allocation: This is the process of reserving memory for your app’s
objects and processes.
Stack Memory: Android uses this for static memory allocation. Local
variables, references to objects and primitive types are common examples of
static memory. Each thread has its separate stack, organized in a LIFO order
(last in, first out). The unified stack size on Android Runtime for both Java
and C++ is around 1MB, which is relatively small compared to Heap memory.
StackOverflowError occurs when an app hits its stack memory limit.
Heap: Android uses this for dynamic memory allocation. The heap is a piece
of memory where the system allocates Java/Kotlin objects in no particular
173
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
order. Android runtime limits the heap size for each running application to
ensure a smooth multitasking experience. There is a variation of the heap
size limit among devices and it depends on how much RAM a device has. If
your app hits this heap limit and tries to allocate more memory,
OutOfMemoryError will occur, and will terminate your app.
This is what the memory allocation looks like for a simple application:
Stack Heap
foo() String
Pool
String str
Object param
new
Object()
main()
int = 1 new
Memory()
Object obj
Memory mem
Search for data objects that are no longer referenced and you can’t access
them.
If you’ve coded your app in a way that’s not very memory efficient and allocates
memory faster than the system can collect it, you’ll notice your app is sluggish
and skips frames.
In such cases, your code flow may force garbage collection events more often or
make them last longer than usual. That slows down the rest of the system.
174
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
Eventually, the system might kill your app process to reclaim the memory and
maintain a functional multitasking environment.
You should profile your app memory to avoid these problems, and that’s where
the Memory Profiler comes in handy!
View the real-time status of allocated objects and garbage collection events
on a timeline.
Initiate garbage collection events.
And more…
175
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
3. This will start a new profiling session for PodPlay from the current time.
Select MEMORY from the right pane as highlighted below:
This will open the actual Memory Profiler toolset from Android Profiler. Take a
minute and have an overview of each of the highlighted sections as numbered
below:
1. The process and device that you are currently profiling using Memory
Profiler. You can see your app’s package name here.
2. The Sessions pane shows your current session, if any. This pane can save
Profiler data as sessions until you quit Android Studio. The sessions pane
allows you to:
176
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
Start a new profiling session.
3. The zoom-in/out buttons control how much of the memory timeline to view
or jump to the real-time updates.
4. The event timeline shows user inputs or actions such as volume changes or
screen rotations performed while profiling the app.
5. The memory graph displays memory that each category uses in different
colors, i.e., Java, Native, Graphics, etc. The horizontal axis represents the
passage of elapsed time since you started profiling. You can see the number
of allocated objects using the numbers on the vertical axis.
Memory Count
To see overall memory usage by PodPlay at any point since the app launched,
put the cursor above the event timeline. You’ll see the memory count of your
app segmented into several categories as follows:
177
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
with the CPU, not dedicated GPU memory.
Stack: Memory used by both native and Java stacks in your app. When your
app invokes a method, it creates a block in the stack memory to hold local
primitive values and references to other objects in this method. This
normally relates to how many threads your app is running.
Code: Memory used for code and resources such as dex bytecode, .so
libraries and fonts.
Allocated: The number of Java/Kotlin objects your app has allocated. Objects
allocated by C/C++ code aren’t counted here.
Note: Even if you’re not using C/C++ in your app, you might see some native
memory used because the Android framework uses native memory to handle
various tasks, such as loading image assets, though the SDK methods for
those were in Java or Kotlin.
The numbers you see are based on all the private memory chunks that your app
has consumed. This count doesn’t include memories shared with the system or
other apps.
The types of objects allocated and how much space they’re using.
The stack trace of each object allocation, including the information about
their corresponding threads.
When the objects were deallocated, this is only available for devices with
Android 8.0 or higher.
178
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
Then enter something into the search bar in PodcastActivity . That’ll show a
list of podcast channels based on your search query. Tap any item from the list,
it will display details about the selected channel in PodcastDetailsFragment as
follows:
The Memory Profiler recorded all your actions and memory allocations. Your
recorded session will look like this:
179
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
1. The events you performed logged there, such as keyboard inputs or taps.
That’s how an event timeline displays user events in real time for a session.
Now it’s time to learn more about the memory allocations table.
The Class Name column explains itself. Look at the other columns in this table:
Total Count: Number of total objects remaining for that class in the session.
Shallow Size: The total size in bytes of all instances of the class in the
memory.
Shallow Size Change: Difference in Shallow Size since the last garbage
collection or memory deallocation happened.
It’s easy to browse the list to find objects with unusually large heap counts, for
example, Bitmaps. Click the Shallow Size column header to sort the list by
largest memory allocating classes.
Note: Click any column header in the list to sort results by that field.
180
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
To find known classes quickly, enter a class or package name in the search field:
If your search query is case-sensitive, check the box next to Match Case
Check the box next to Regex if you want to use regular expressions.
You can also search by package or method name if you select Arrange by
package or Arrange by callstack from the dropdown menu on the left of the
search field.
The PodPlay app uses the Glide image loading library to download images, also
known as Bitmaps, from remote servers and handle the image loading on
separate threads. That keeps PodPlay’s Main thread free and always responsive
to user interactions. You might be wondering what that looks like in memory.
Switch to the Visualization tab in the highlighted area below, and you’ll be
amazed to see the organization of all the Bitmap-related threads in the memory:
The above image shows the Main Thread and other threads from Glide to load
Bitmaps with their method call-stack.
To see even more details from the list, switch back to the Table tab and click a
class name, for example, BitmapDrawable. A new pane will open below the list
as shown here:
181
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
1. Instance List: A list of all the instances of your selected class, in this case -
BitmapDrawable, including their allocation/deallocation time and the
memory each instance consumed, namely, Shallow Size.
2. Instance Details: This section shows the allocation of that instance and
which thread it’s in. This is the call stack for that object. From the above
image, you can see the BitmapDrawable being fetched with a get() call
from the Glide library, all from the main thread.
That’ll free up some memory. It will mark the moment garbage collection
occurred in the timeline as shown above. See if you can find the difference in
memory allocation using the skills you just gained!
182
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
Full: This captures all object allocations in memory. A downside of this
option is that if you have an app that allocates many objects, you may observe
visible slowdowns with your app while profiling.
You can end the sessions now by clicking Stop in Memory Profiler.
Forced garbage collection occurs when the app allocates but also has to
deallocate objects in a short period. It can, for example, happen if you allocate
heavy objects like Bitmaps in loops. In each iteration, they’ll keep saturating
your heaps. The system not only has to allocate a large object but it also has to
deallocate it from the previous iteration, so it doesn’t run out of memory,
resulting in more garbage collections. This situation is a Memory Churn. Users
may notice stuttering or slowdown in the app because of this frequent garbage
collection.
A Memory Leak happens when your code allocates memory for objects but
never frees that memory or is unable to deallocate it. Over time, the memory
allocated for these objects turns into a large, immovable block, forcing the rest
of the app to operate in what’s left of the total heap memory. If this continues,
eventually, the app can run out of memory and crash.
Unreferenced Referenced
Label
Objects Objects
Unused Objects
Memory leaks can be huge and obvious, such as when your app is trying to load
a high-resolution Bitmap that’s larger than the available memory. Some
183
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
memory leaks can be hard to find, so the user will only notice the app lagging
over time. Capturing heap dumps and analyzing them can help to figure out
such memory leaks.
The types of objects your app has allocated, and how many of each type.
Note: You can capture heap dump while recording allocations and get a
referent call stack with Android 7.1 or higher only.
2. Run Android Profiler and, in the type dropdown, switch to the Memory
section.
6. In the Memory Profiler, select Capture heap dump, then click Record.
184
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
While dumping the heap, you may observe the amount of memory getting
increased temporarily. You should expect this because the heap dump occurs in
the same process as your app and requires some memory to capture the data.
Take a moment to understand what each of the components in this panel offers.
The red circle on top displays a timestamp. It shows the elapsed time from the
app launch to the moment you captured the heap dump.
Now, look at the numbered areas. You can see the explanation for each of them
below:
4. Native Size: Total amount (in bytes) of native memory used by the object
type. You’ll see memory allocation for some Java objects here because
Android uses native memory for some framework classes, such as Bitmap .
5. Shallow Size: Total amount (in bytes) of Java memory used by this object
type.
6. Retained Size: Total size (in bytes) of memory being retained by all instances
of this class.
185
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
The first dropdown menu on the left lets you choose which heap to inspect:
View all heaps: Shows data for all the heaps when the system specifies no
heap.
View app heap: The primary heap where your app allocates memory.
View image heap: The heap for the system boot image. This contains classes
that the app preloads during boot time. Allocations here never change or go
away.
View zygote heap: The copy-on-write heap where an app process is forked
from in the Android system.
With the dropdown menu in the middle, you can choose how to arrange the
allocations:
Arrange by class: Groups all allocations based on the class name. This the
default selection.
Arrange by package: Groups all allocations based on the package name.
Arrange by call stack: Groups allocations into their corresponding call
stack. This option only works if you’ve captured the heap dump while
recording allocations.
The next dropdown menu lets you filter displayed classes based on the below
criteria:
Show all classes: Displays all the classes, including base classes, regardless
of whether it’s initiated from the system or the app.
Show activity/fragment Leaks: Displays class names of the Activity or
Fragment if it’s creating a memory leak.
Show project classes: Displays classes only from the source code in your
project.
186
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
as the profiler is running. You lose the heap dump as soon as you exit the
profiling session.
So, before fixing the memory leak, save this heap dump so that you can
compare after applying the fix.
To save a heap dump, hover the cursor on the Heap Dump entry in the
Sessions pane, and click the save icon as highlighted below:
An Export As dialog will appear. Save the file in your desired location. The saved
file will have .hprof extension.
To import the saved file, click Start new profiler session ▸ Load from file…
Note: You can also import a .hprof file by dragging it from the file browser
into the Memory Profiler panel.
Activity instances that have been destroyed but are still being referenced
from somewhere.
187
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
Fragment instances that have been destroyed or don’t have a valid
FragmentManager but are still being referenced.
There are two ways you can see memory leaking Activity or Fragment right
away:
Click PodcastDetailsFragment from the list. That’ll reveal more detail below
about the memory leaking instance as follows:
Looking at the Instance Details section on the right pane indicates that the
whole PodcastDetailsFragment instance might be retaining in the memory
even after it’s supposed to be destroyed, causing the memory leak!
Now switch to the References section, then check Show nearest GC root only:
That gives you enough clues that there’s something wrong with the
episodeListAdapter instance.
188
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
Note: To know more about GC Root (Garbage Collection Root), you can refer to
Android Memory Profiler: Getting Started.
Well, now you know the root cause of the memory leak. To fix that, move the
episodeListAdapter out of companion object and place it on the top of the
class.
Now re-launch the app, and capture a heap dump with the same steps
mentioned in that section. You’ll be glad to see that the memory leak’s finally
gone!
You should stress your app code and try forcing memory leaks, especially
before you release the app. Below are some ways you can provoke memory leaks
in your app:
To run the app for an extended period before doing a heap dump. The
smaller the leak, the longer you’ll need to run the app to find it.
Rotate the device multiple times in different activity states. The system
recreates the Activity / Fragment during a screen orientation change. If
your app has a reference to one of those objects, the system can’t garbage
collect it, and it’ll mark it as a memory leak.
Switch between your app and another app while using different UI
conditions. For example, navigate to the Home screen, then switch back to
your app several times.
189
Android Debugging by Tutorials Chapter 10: Profile Memory Usage
Reference a view in an async callback, you can’t free that view until the task
is done.
Reference views from static objects. Static objects stick around for the
lifetime of the application.
Put views into collections, that’s very expensive in terms of memory
allocation.
Allocate objects in inner loops. Do it outside the loop, or redesign to skip
allocation in the first place.
Key Points
Memory Profiling keeps app performing at its max. Profiling your app at
different stages of development can result in finding memory leaks early.
In the next chapter, you’ll learn to profile network activity, so good luck on your
next adventure.
190
Android Debugging by Tutorials
You’ve previously learned how to look into memory footprint using the Android
Studio Memory Profiler as a part of your debugging process. In this chapter,
you’ll continue your quest and learn to inspect network traffic using the
Network Inspector bundled with Android Studio.
This is cool but can be overwhelming for your device if the app performs
frequent, unnecessary network operations. To minimize your network data
usage and resource consumption, you need to inspect your network activity to
keep it optimal.
1. Open the Podplay starter project and run the app on an emulator or
connected device using API level 26 or higher.
2. Select View ▸ Tool Windows ▸ App Inspection from the menu bar.
191
Android Debugging by Tutorials Chapter 11: Profile Network Activity
192
Android Debugging by Tutorials Chapter 11: Profile Network Activity
You won’t see much at this point apart from a blank timeline moving at the
bottom of the Network Inspector panel. That’s your network timeline, and soon
you’ll be able to make the most out of it.
You’ll see a spike in the network timeline as above. Click the Pause button in the
top-right corner of the Network Inspector panel. This will stop your network
request (this spike) from moving away with time.
Now, take a moment to analyze what’s going on with the network request to get
a deeper understanding.
The Network Inspector displays a few more buttons that give you control over
the timeline. The utility of these buttons is as follows:
193
Android Debugging by Tutorials Chapter 11: Profile Network Activity
1. Zoom Out: Zooms out the timeline, and displays a longer period of time to
allocate more requests, if there are any, in the timeline view.
2. Zoom In: Zooms in the timeline displaying a shorter period of time in the
timeline view.
3. Reset Zoom: Resets any zoom-out or zoom-in over the timeline view and
reverts to the default zoom state.
4. Zoom to Selection: This allows you to zoom into any selected interval from
the timeline. This is helpful to focus on a specific time frame you select to
identify a network request which occurred within that period.
As you can see, your network request has been made in a period between 5 to
10 seconds after launching the app, select that interval from the timeline and
click Zoom to Selection. This will reveal another panel below the timeline, with
two tabs — Connection View and Thread View.
These tabs expose detailed information about the network requests you’ve
made within the selected interval.
Note: Put your cursor over any point on the network request to view sent and
received data at that moment in the timeline.
Connection View
The Connection View tab displays files or network requests sent or received
during the selected period in the network timeline, irrespective of threads. You
can inspect each request’s name or query, size, type, status and transmission
duration.
194
Android Debugging by Tutorials Chapter 11: Profile Network Activity
1. Name of the network request. This column shows the request URL except for
the hostname part. Hover the cursor over the request name to see the full
request URL.
3. The type of content received from the request. In this case, it’s Javascript in
JSON format.
4. HTTP Status Code for this request. It indicates whether a specific HTTP
request has been completed successfully or had any errors. The selected
request was successful and returned with a proper response; hence it’s
showing 200. You can learn more about HTTP Status Code in different
scenarios here.
5. The time taken for the whole network operation to complete. According to
this, it took a total of 413 milliseconds to execute the request and receive a
response from the server.
6. Duration for the selected request in the timeline. You can see the network
operation occurred for less than a second, 413 milliseconds to be specific,
sometime between the 5th and 6th second of launching your app.
7. The range in the top-right corner denotes the selection time frame from the
network timeline. In this case, you displayed network operations executed
from the 4th to the 7th second in the timeline.
Note: You can sort the list in Connection View by selecting any of the column
headers. For example, to sort the network requests based on the size of data
transferred, select the Size column.
Thread View
The Thread View lists all CPU threads that have initiated a network activity
from your app. Switch the tab from Connection View to Thread View, and
you’ll see a panel like this one below:
195
Android Debugging by Tutorials Chapter 11: Profile Network Activity
In the Thread View, you can see the OkHttp framework has been used to create
and manage network requests from PodPlay. OkHttp handled the threading
mechanism and concurrency for all network operations from within the app.
You’ll get more details on threading later in this chapter.
The Thread View contains the timeline column as well. With this cool feature,
it’s easy to visualize what happened on the thread for network operations
between the 5th and 6th seconds.
Again, hovering your cursor over the selected network thread will display the
request parameters and duration for that operation, as shown above.
Inspecting Details
The Network Inspector offers even more details about the Request, Response
and Threads for each individual network operation.
Overview
Switch back to the Connection View tab, then select the first row. You’ll see a
new panel on the right side:
This is the Overview which holds a lot of interesting details about your network
request. It contains 3 main sections:
1. Displays the response received from the server for this particular network
operation. In this case, it’s a JSON object holding an array of podcasts that
include your search query “RW”.
196
Android Debugging by Tutorials Chapter 11: Profile Network Activity
2. Presents details about the request made from the app. Looking at this
section, you can extract info such as the full URL , the request method,
whether it’s a GET, POST, PUT or another type of request, the status code,
content type or size of data exchanged for the request.
3. Shows a bar with an overview of exchanged data for this network operation.
Don’t let the name confuse you, this bar explains that the network operation
initially sent a small amount of data to make the request, then received a
larger portion of data over the time of execution. The orange segment is sent
data and the blue segment is received data proportionally.
Response
Now, switch to the Response tab. The Response tab presents the response
headers and the full response body. The response headers hold additional
information about the server or how the response is cached:
That’s a lot of information! You might want to focus on a few important areas
highlighted above:
2. Content: Content headers contain the size, type or encoding info of the
response object. You can see content-length is 9551 bytes. This is especially
useful when you’re downloading a file. The content-type is the MIME type of
the content. It indicates text is the type and javascript is the subtype of the
response.
197
Android Debugging by Tutorials Chapter 11: Profile Network Activity
3. Status: In most of the cases, you’ll just need to check the date or response-
status-code to verify the time you’ve received the response and if you’ve
received it successfully. Status code 200 means it was successful.
Body is the most important section here, which displays the complete response.
It receives the response as a JSON object in this case. The panel displaying it
allows you to scroll down and check the full object.
Request
In the Request tab, you don’t get as many details as in the Response tab. This
tab has two sections: Application Headers and Body.
Encoding: Accept-Encoding tells the server that the app can accept
compressed output like gzip for the response.
Connection: A default HTTP connection is usually closed after completing
each request. Keep-Alive header, also known as HTTP persistent connection, is
kind of a communication message between the app and server that says:
“You may grab many files as long as the connection is alive.” This
configuration is handy if you request something that includes multiple files,
such as a web page. Setting Keep-Alive transfers all the necessary files
198
Android Debugging by Tutorials Chapter 11: Profile Network Activity
through a single connection request instead of creating and closing multiple
connections.
Host: The domain name or the server’s hostname you’ve requested. In this
case, it’s itunes.apple.com.
Agent: user-agent represents a software or framework that retrieves,
renders and facilitates interaction with the web content on behalf of the user,
the PodPlay app. You can see PodPlay relies on the OkHttp library to create
and manage network requests for you. It also displays that the version code
of OkHttp library is 4.8.1.
Body: Since it was a GET request, the request body isn’t available in this case.
You could see the request body submitted to the server in this section if it
was a POST or PUT request. The pane is useful to verify if the request body
submitted is what the server expects when creating a POST or PUT request.
Call Stack
The fourth and final tab in the network details panel is Call Stack. Switch to that
tab, and you’ll see a stack of threads that corresponds to the network operation
as follows:
As you already know the OkHttp library is handling threading for the network
operations. You can see it’s running a Worker Thread using
ThreadPoolExecutor. This ensures concurrency when there are multiple
requests.
By inspecting the call stack, you can also tell that the threads for network
operations, and worker thread, are running on top of the Java main thread, also
known as the UI Thread. This isolates the network process from the main
thread. That’s the trick that keeps your app responsive while running an
expensive network request in the background!
199
Android Debugging by Tutorials Chapter 11: Profile Network Activity
That’s strange, but this situation occurs when the Network Inspector detects
traffic values yet can’t identify any supported network requests. In such cases,
you might want to log your network operations to see what’s happening.
import com.yourcompany.podplay.BuildConfig
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import java.util.concurrent.TimeUnit
companion object {
val instance: ItunesService by lazy {
// 1
val client = OkHttpClient().newBuilder()
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
// 2
if (BuildConfig.DEBUG) {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
client.addInterceptor(interceptor)
}
200
Android Debugging by Tutorials Chapter 11: Profile Network Activity
// 3
val retrofit = Retrofit.Builder()
.client(client.build())
.baseUrl("https://itunes.apple.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
retrofit.create(ItunesService::class.java)
}
}
The above code has three changes to the previous implementation, it:
3. Uses the OkHttpClient object with your Retrofit builder, which is responsible
for communicating with the server.
Relaunch PodPlay and look into the Logcat from the IDE. Search for the term
“OkHttpClient”.
Now you’ll be able to see the request and response made from the app getting
logged:
Highlight the request from the logs you have. You can clearly see the GET
request ends with term= , which means the request has been made with no
search term, with an empty string!
Since there’s no search term provided in the request query, you can see that the
response also returned as an empty array instead of a proper response.
This certainly looks like a programming error, but it gives you a clue! Look for
places where a network has been made with an empty input.
If you look back, this unusual network request was executed shortly after you
launched the app. So look for onCreate() inside PodcastActivity.kt, which is
the entry point when you launch PodPlay.
201
Android Debugging by Tutorials Chapter 11: Profile Network Activity
Remove performSearch(term = "") and launch the app again. Observe the
network timeline and Logcat and notice there is no weird network call anymore.
Key Points
Network Inspector offers a set of tools showing all the details available on
network operations.
Network timeline is the key to looking for network activity at any point since
you launch the app.
You can leverage Connection View and Thread View to look into different
aspects of a network operation.
Logging HTTP request and response data helps if you still can’t find enough
information through Network Inspector.
202
Android Debugging by Tutorials Chapter 11: Profile Network Activity
Don’t stop here! In the next chapter, you’ll learn to use Energy Profiler to
optimize your app for minimizing resource consumption. Good luck on your
next adventure!
203
Android Debugging by Tutorials
In the modern day, we can’t live a single moment without our smartphones.
Most of the time, we’re either messaging, listening to music, watching videos or
snapping pictures with our phones! To serve our needs all day long,
smartphones are getting more and more powerful in terms of CPU, battery or
storage. Even after having a smartphone with huge battery life, we all see the
‘Low Battery’ alert or run out of battery frequently. This indicates that no
matter how powerful your phone is, the apps you use also need to smartly
manage energy consumption so you can keep using them as long as you need.
In the previous chapter, you learned to profile network activity and optimize
network resource consumption.
In this chapter, you’ll learn to inspect energy consumption with the Energy
Profiler that comes with Android Studio.
Energy Profiler appears as a row in the Profiler window once you run your app
on a connected device or Android Emulator running Android 8.0 (API 26) or
higher.
1. Open the Podplay starter project and run the app on an emulator or
204
Android Debugging by Tutorials Chapter 12: Android Energy Profiler
connected device using API level 26 or higher.
2. Select View ▸ Tool Windows ▸ Profiler from the menu bar.
3. This will start a new profiling session for PodPlay within the Android Profiler.
Click over the ENERGY timeline from the right pane as displayed below:
This will open the actual Energy Profiler, and it’s a detailed representation of the
energy consumption from your app in real-time. The new panel will look like
this:
As shown above, the Energy Profiler panel includes the following main
components:
1. Sessions Pane: Displays your current session info, such as starting time,
205
Android Debugging by Tutorials Chapter 12: Android Energy Profiler
elapsed time in seconds, and the device name. The sessions pane allows you
to start a new profiling session or stop the current session. If you re-launch
the app, a new session will be added to this pane with the current timestamp.
The session data remains here until you quit Android Studio.
2. Event Timeline: This timeline shows user interactions with the device, such
as keyboard inputs, pressing hardware buttons or screen rotation events.
3. Energy Timeline: This shows the estimated energy consumption from your
application code. This includes CPU loads, Network activity etc.
4. System Timeline: System events that may affect energy consumption are
displayed in this timeline. They could be alarms, scheduled jobs or wake
locks. You’ll get to more details about system events shortly.
5. Quick Preview: Using your cursor, hover over any point on the Energy
Timeline. It’ll display a quick preview of energy consumption from your app
at that point.
6. Timeline Controls: These buttons offer you control over the timelines. The
utility of them are as follows:
Zoom Out: Zooms out of the timeline, and displays a longer period to allocate
more requests, if there are any, in the timeline view.
Reset Zoom: Resets any zoom-out or zoom-in over the timeline view and
reverts to the default zoom state.
Zoom to Selection: This allows you to zoom into any selected interval from
the timeline. This is helpful to focus on a specific time frame that you select
to identify a network request which occurred within that period.
Attach to Live: This button allows you to pause the timeline at any point or
resume displaying the real-time updates jumping to the end of the timeline.
Note: Selection over the timelines in the Energy Profiler is not allowed, so
Zoom to Selection will remain disabled.
206
Android Debugging by Tutorials Chapter 12: Android Energy Profiler
energy and defense mechanisms that protect the device from unnecessary
energy drain. You need to clearly understand these to make your app energy-
efficient.
System events that are displayed in the Energy Profiler fall into the categories
below:
Wake Locks: There are cases when an app needs to keep the CPU or the
screen awake to complete some work. For example, a video-player app might
keep the screen on even when there’s no user interaction for a while. A
‘wake lock’ is a system for keeping the CPU and screen on and in use for
such cases. Otherwise, the device would go to sleep after a certain time to
save energy.
Alarms or Background Tasks: Using AlarmManager to schedule
background tasks to be executed in the future or at regular intervals is a
common approach in Android. However, it’s not the best way to do so. When
an alarm’s triggered, it may wake up the device and run energy-consuming
operations.
Doze Mode
From Android 6.0 (API level 23) onwards, Android introduced two power-saving
features that extend battery life by managing how apps behave in different
conditions. One of them is putting the device on Doze mode when it’s not
connected to a power source, in simple words, charging.
Below are the restrictions applied while the device is in Doze mode:
207
Android Debugging by Tutorials Chapter 12: Android Energy Profiler
Ignores wake locks.
Standard AlarmManager alarms are deferred.
The system performs no Wi-Fi scans.
You must be thinking, how does the system resume these operations then?
Well, the system exits Doze for short intervals to let apps complete their
deferred activities. During this time, the system runs all pending syncs, jobs,
alarms and so on. This period is the maintenance window.
The diagram below makes it easy to visualize how Doze mode works:
time
At the end of each maintenance window the system enters Doze again,
suspending activities mentioned earlier. The system schedules maintenance
windows less frequently over time. This helps to reduce battery consumption in
cases of longer-term inactivity when the device isn’t connected to a power
source.
As soon as the user uses the device by moving it, turning on the screen, or
connecting a charger, the system exits Doze, and all apps return to normal
behavior.
App Standby
App Standby is another energy-saving technique that’s used to prevent
unnecessary system events. It’s a state where the system determines if an app is
idle or not in use. When in the standby state, apps are deferred from
208
Android Debugging by Tutorials Chapter 12: Android Energy Profiler
An app goes to the standby state when the user doesn’t interact with the app for
a certain period, and none of the following scenarios occur:
The app has any foreground process, either as an activity or service or being
used by another activity or foreground service.
The app receives a notification or generates a local notification.
The app is an active device admin app, such as a device policy controller.
Device admin apps never enter the App Standby state because they must
remain available to receive policy from a server at any time.
The system reverts apps from the standby state when the user launches the app
again or plugs the device into a power source, allowing them to access the
network or execute any pending jobs and syncs. If the device is idle for a long
time, the system allows apps on standby to access the network about once a day.
Run the app and go straight to the Energy Profiler by clicking the ENERGY
timeline.
Observe the Energy and System timeline in this panel — all seems to be normal.
Try performing some energy-intensive operations that the app’s supposed to do,
for example, playing podcasts, by following these steps:
209
Android Debugging by Tutorials Chapter 12: Android Energy Profiler
3. Start any podcast by tapping an item. The podcast player will launch. That’s
EpisodePlayerFragment :
210
Android Debugging by Tutorials Chapter 12: Android Energy Profiler
Look at your Energy Profiler timeline. Now, you’ll see some anomalies there:
But what’s the long, red-colored bar in the SYSTEM timeline? That indicates
something needs to be taken care of!
The System timeline displays a color-coded bar for the time range when a
system event is active. Different color codes denote the different types of system
events:
211
Android Debugging by Tutorials Chapter 12: Android Energy Profiler
If you look carefully at the wake lock, it clearly hints that the wake lock has been
called from EpisodePlayerFragment at line 441.
As a media player app, PodPlay may use a wake lock to keep the CPU and screen
awake while you’re listening to the podcasts, but it’s also important to release
the wake lock as soon as the user leaves the player screen. That’ll save energy
consumption and prevent the display from being lit unnecessarily.
That’ll release the wake lock whenever the user quits the player.
Now relaunch the app and play a podcast following the steps above. Then tap the
back button until you see the podcast list screen again.
Observe the System timeline for a while. It’ll update like below:
The highlights above confirm the energy event, wake lock, ended shortly after
you tapped the back button to navigate away from the player screen. You can
extract more info from the displayed logs, such as the process ID (pid),
timestamp, and callstack.
You can play around a bit more or even try to remove the wake lock and see how
that impacts the app through the Energy Profiler.
212
Android Debugging by Tutorials Chapter 12: Android Energy Profiler
Ensure wake locks are released as soon as they are no longer needed.
If the app is performing any big HTTP downloads, such as media files,
consider using DownloadManager.
If the app needs to synchronize data periodically from a server, use a sync
adapter.
Key Points
Use the Energy Profiler to monitor, inspect and detect energy-related issues.
Check out detailed energy stats at any point with the Energy and System
events timeline.
System events are resource-hungry! Be mindful while using them.
In this final chapter, you learned how to make the best use of the energy that
your smartphone offers you. There’s always more to learn, so continue your
quest for energy with these advanced topics:
Power Management
213
Android Debugging by Tutorials
13 Conclusion
Congratulations on completing your debugging journey! It’s been a long way —
from learning the basics of Android debugging tools to handling app
performance issues.
At this point, you have gained a lot of experience with debugging tools and
techniques. But don’t stop here! Hopefully, these techniques will inspire you to
get more ideas about possible solutions. You’ll also be faster in detecting issues
and fixing them because now you know how to gather more information about
the source of the problem.
One thing’s for sure — with this knowledge, you’ll move your app to the next
level!
In case you’re interested in more debugging content on our site, check out the
Beginning Android Debugging video course about debugging basics, Android
Debug Bridge (ADB): Beyond the Basics for an extended version of the ADB topic
and Android Memory Profiler: Getting Started for learning about memory
management.
If you have any questions or comments as you work through this book, please
stop by our forums at https://forums.raywenderlich.com and look for the
particular forum category for this book.
Thank you again for purchasing this book. Your continued support is what
makes the books, tutorials, videos and other things we do at raywenderlich.com
possible. We truly appreciate it!
214