[MUSIC PLAYING] DAVID HERMAN: Hello, everyone. Welcome to the deep dive
into Android Studio Profilers session. I'm David Herman. SHUKANG ZHOU: I'm Shukang Zhou. We are engineers
from the Profiler team in Android Studio. DAVID HERMAN: So before
we really got into it, we wanted to give
you all an outline of what to expect in this talk. Instead of sort of coming
from a high overview, instead we're going
to more narrowly focus on a few features that we
think can help you get a better handle on any codebase. We're also going to drop some
tips and tricks along the way. We're going to be profiling
a real app, Santa Tracker. Santa Tracker is
an app by Google which allows users to
track Santa on his course around the world
on Christmas Eve. The app also contains games,
and a couple of other extras. They release a new
app every year. We're going to be
using the one that is publicly available on GitHub. Finally, I want to mention
two talks about profilers that were previously given
this year, one at Google I/O, which did talk profilers
at a higher level, also introducing what
was new in Android Studio 3.2 for profilers. And another one at
the Android Game Dev Summit, about
profiling your games. That talk focuses
a little bit more on performance, native
code, related tools, things like that. You can find those
videos on YouTube. If you're watching
this online, we've also included links to those
videos in the video description below. If this is your first time
learning about profilers-- you're just curious-- here's a very quick overview. The Studio Profilers
feature is divided into four main profilers,
one for CPU, one for memory, one for network,
and one for battery. There's also an
Event Profiler that's always on the top, which
lets you see things like user events such as
taps, keyboard events, screen rotations, and
lifecycle events-- so when you're fragments and
activities start and stop. Anyway, that's enough talking. Let's jump into the demo. SHUKANG ZHOU: OK,
let's start the demo. So first, with one click on this
button, I can launch the app and start profilers. So today, we are
using Android Studio 3.4 Canary 3, because that's
the latest of what we have. Since this is a Canary release,
it's not a stable release yet. So there might be some bugs. Please bear with us if anything
interesting happens today. The app we are using
today, the Santa Tracker, contains only Java code. So the Java apps will be
the focus of today's talk. So as you can see
in the profiler, we have four profilers, CPU,
Memory, Network, and Energy. You can click on any of them
to get more detailed data from that profiler. Let's jump to the CPU profiler. So here, as you can
see in the CPU profile, you can see the CPU utilization
and the thread state. It will be useful
to tell you when your app becomes CPU-bounded. And if you are examining two
related threads, one trick is-- here-- you can reorder the
threads any way you want. To know further data information
about which part of your code is executing, or how
they are executing, you need CPU recordings. And let me get a
little bit more space. So as you can see
here, there are four types of CPU recordings. So let's go to the first one. It's the sample Java method. Let's get a quick
recording on this one. So during this type
of CPU recording, the Java Virtual
Machine periodically will collect the call stacks
of all the other Java threads in your process. And then it will
present the stacks in this part, which is the
details of the recording. And after the recording is
done, the entire recording is automatically selected. And if you want to
take a closer look, there is a button called
Zoom to Selection. So if I click it, it will
fill the entire screen. And if you want to just see
a subrange of the recording, you can select using your mouse. If you want to select the
entire recording again, you can click this
small clock icon here. And sometimes you only want
to see a very specific point. In that case, you can to a
single click in this area, and you automatically
select something here. Now let's take a look
at the call stacks here. And let's select this range. That might be more interesting. So as you can see,
the profiler will color the cost from the Android
platform in orange, the method from the Java language
in blue, and it will also color any of your code and
the library code to green. So if you want to know
what method from your code is running, you will be
looking for the green stuff. So here we see some
green stuff here. And here, if you can see,
this is onDraw method from the Village View class. And if you see the codebase,
as you can easily see, the visit [INAUDIBLE] is
responsible to draw the clouds that is on this screen. So another thing I want
to talk about the sampling is you can do some customization
about this recording type. So you click this Edit
Configuration entry, and you can click
this plus sign. You are able to create
customized CPU configuration. For this sample Java
method recording type, you can change the
sampling intervals. So as most sampling
techniques go, the more frequently you
collect samples the more likely the data will be representative. However, that will
incur more overhead. So sometimes, depending
on your use case, you may want to
have several tries before you find the sweet spot. So as we have seen,
the sample Java method is very useful to get a
high-level picture of which part of the code it is running. In some other
scenarios, if you want to focus on a smaller
area, the second type of the recording,
trace Java method, will be more interesting. So let me collect another case. So in this type
of CPU recording, the Java Virtual Machine is
collecting the data every time when the execution enters
a method or exits a method. So therefore, there is a lot
more data being collected. So for example, if you really
want to try and understand exactly what methods
from my code are running, I will look for the
green stuff here. And you can see there
are a lot of things. And you may want to zoom in. So you can use the
W-A-S-D keys to zoom in. So the W will be the zoom in. As you can see, the
key I'm pressing. The S will be zoom out. The D will be
moving to the right. A will be moving to the left. So if you're a gamer, you
probably already know this. If you zoom in,
let's keep zooming in to see what are
these green stuff. So here we seen
in this, a big one is the one we just saw before. It's the onDraw method
from the Village View. They are responsible
for drawing the clouds. Here, this is the onDraw method
from the Snowflake View class. They are responsible to draw
all these snowflakes that are floating around. And if you look closely, there
are many very tiny green lines here. So you can further zoom in. You can further zoom in. So you can see this
is the onDraw method from the Snowflake class. This is also from
the snowflake class. This is also from
Snowflake class. So you may want to see what
exactly does this class go. So you Right-Click here,
and you can have a menu to jump to the source code. So you can see here, in
this Snowflake class, in this onDraw
method, we are doing some calculation about the
velocity, about an angle, about the size. Then we draw a circle. Then it's clear that every
snowflake on this screen will execute this method. That's why we are seeing so
many calls into this method. So as you can see,
trace Java method is very useful to
verify whether or not a method has been executed. It's also useful to verify how
often that method is executed. Another thing I want
to talk about here is about the four caps here. So the first one
is the call chart. So the call chart,
as we said, is representing all the call
stacks during the CPU recording. So the things to the left-- well, let's select
the entire range. You can see it. So the call stacks
show up the left, where it happens first
in this recording. So the things to the right will
happen later in this recording. In the frame chart,
it7s similar to the call stacks, but it's upside down. So the root is at the bottom. And also, identified call
stacks are aggregated here. So it's very easy to see
the total time of method has been executed. The top-down is [INAUDIBLE]
has exactly the same data as the frame chart. It7s just represented
in a different view. So what's nice
about this view is you can sort of these
methods by the time. So the save time is
the time executing by this method itself. The children is the time
executed by the subroutines called by this method. And the total column is
the combination of the two. Bottom-up is looking at the call
stack upwards to the caller, basically from the method
to another function that called it. So here, it's very useful. If you expand it,
it's very useful to see where this
method is called, and how much time this method
has executed when it is called by that specific caller. And there is a third
type of CPU recording that is sample
native functions-- C or C++ functions. And let's do also
another short trace here. And if you remember, I just
said this app, Santa Tracker, it contains Java-only code. So therefore, the call stacks,
assuming the call stacks collected by this
type of recording is not very interesting. They are mostly
the system calls. Some of them use the Android
framework native code. So however, if your app
has any native components, this type of recording
will be very handy. There is another type of CPU
recording called trace system to our calls. But before I go into
the details of that, I will hand the
demo back to Dave. DAVID HERMAN: Excellent. So CPU recordings
are very useful. However, sometimes there's an
exact function, or maybe just a couple of functions that
you know you want to analyze. And it's a little bit imprecise
to record, do something in your app, stop recording,
and then zooming in and search for it. Fortunately, we provide a
very simple solution for this. And I already added the code in. So let's talk about it. So the debug class is actually
part of the Android API. And the debug class
and many of its methods have, in fact, been
in the Android API since the very first
version, including these two. What the start method
tracing function does is it asks the system
to take a trace, save it with the filename you
provide, and then it puts it in a folder somewhere that you
can pull off your device later and inspect. That's really nice, but
on Studio profilers, we've got your back. We'll do all of
that work for you. So if you were
doing this manually, you'd probably be very careful
about the name that you chose. And if you're doing
multiple traces, you'd maybe choose
unique names so that they didn't overwrite each other. In our case, the name is not
going to show up in Studio. We don't really care about it. So call it whatever you want. Here, I'm going to start
doing it putting a recording around this function. So what I'm curious
about in this case, there's an activity in Santa
Tracker called the City Quiz. And the City Quiz loads
files from the disk. That's usually a
really good thing, where you want to know how
long it's going to take, and if it's doing
anything suspicious. Maybe it's not. We're going to take a look. But one thing I
do want to mention is when you call this start
method tracing function, it's doing a trace of your code. This is the more expensive,
precise, detailed one. So I'd be very careful. I wouldn't do this around
a large amount of code, just to make sure
that it doesn't take longer than you might expect. All right, let's actually
go into the City Quiz, which is down here. See what happens. Now, before I do this, I want
you to keep your eye over here in the Session panel. And you hit Play. It7s going to run in. And it's automatic
going to record. I didn't have to do anything. That's awesome. Let me back out. So as you can see here,
there's nothing new. This is exactly what Shukang
was just showing you before. It's just another trace. But you didn't have
to record it yourself. I think this is a good
moment to call out the lifecycle events
I talked a little bit about at the
beginning of the talk. As you can see, we're
doing a load here. We left our previous activity. And now we're into
this new activity. You can also see that if you put
your mouse over an activity, as of Android Studio 3.3, it will
also include the fragments that are active during that time. So that may be useful for you. But here we are. We're in this space
while we're still loading, before we've
actually entered the activity. And there's another
really great feature that I want to show you here,
which is this Filter button. When I press the Filter button,
it brings up a search box where I can type into it. Now I happen to
know that JSON has a function called readLiteral. So let me just type that in. Now one thing you
might notice here is this part of my
call chart dimmed out. And all parts here are not dim. They're still the normal color. So let me zoom in and see
if I can find where that is. So there we go. So we could see some instances
of this readLiteral function. Basically if my function is
an exact match, it will bold. If it's a function that calls
either into that function or is called out of that
function indirectly, the color will be left the same. And otherwise, it will dim. And it's really
useful to sort of get a good overview of
how much time you're spending in your
code on the function that you care about
versus what you don't have to pay attention to. All of the CPU detail
views support this. So the flame chart has
this similar dimming. Top-down and bottom-up will
strip out those functions that were dimmed. So if you're ever trying to
inspect some sort of method, and you're really
narrowing down on it, please give the
filter option a try, and see if you can sort of
focus on what you're looking at. Now the last thing to
call attention to here is, all of these
traces that we've done, if you mouse over them,
there is a Save button here. So you can actually
export your traces. If you do this, you can
give it to a co-worker, attach it to a bug-- useful things like that. If somebody gives you a trace
file or you are loading one, you can just hit the plus
button over here, and load it from file. All right. OK, cool. SHUKANG ZHOU: Now
I'm going to talk about the last type
of CPU recording, the trace system calls. Trace system calls,
on this feature, was introduced in Android 3.2. It collects fine-grained
system events that's related to an
app7s performance. So you can investigate how your
app interacts with the system resource so that's
a system trace. So one thing-- again, I'm
using the Click to Zoom button here so that it's
very easy to see. One thing I want
to show you here-- the first thing I
want to show you is in this thread state view. So if you click the range,
zoom to that selection again, and I will click here. [INAUDIBLE] So here, as you can see, you
can use your mouse, hover over, you can see this thread state
is runnable, become running, then become runnable,
become running again. So as you can see, we are
collecting every CPU scheduling operation. So at this level of details,
it's very easy for you to figure out exactly when
your thread becomes blocked. And that could be useful if
you have some threading issues. Another thing I want to show
using the trace system costs is to investigate slow UI jank. So slow UI rendering, also
called jank by some people, as you may know, that
is the [INAUDIBLE] UI does the work in two phases. The first phase happens
in the main thread. It determines what
is on the screen, including the layout
and the [INAUDIBLE].. It determines the what
by executing all the UI elements, such as all the
view classes in your app. So after the main threads
generate the what, they are passed to the
native render thread. The render thread will be
figure out how to draw them. Then the how will be passed
to the surface finger system process, and the
hardware who is actually performing the drawings. So that is giving
that background so we can see under
the frame area, we have this main, which
represents the main thread, the render represents
the render thread. So let me zoom out a little bit. So you can see here,
this is the first phase. And corresponding, the
second phase will be here. If you are targeting
a smooth UI-- smooth animation at
60 frames per second, which is roughly 16
milliseconds per frame. So the two phases
combined together should be under 16 frames. If it is longer than
that, the profiler will color that frame to red. So as you know, this
is something slow. So if we zoom out more, you
can see a lot of frames. I think every one today is red. That means every
one is exceeding the 16 millisecond threshold. One factor is we are
using the emulator. Because the way the emulator
interacts with the system, you are going to
see more red frames than from our actual device. Before today's talk, I have
collected another trace using an actual physical device. And I have exported
that trace as a file. And now I want to import
that trace to show you. So as Dave said before,
you can use this plus sign to import a trace. That7s this trace. Because when you
trace system calls, everything-- the system-wide
events are collected. So you need to tell
the provider which process you want to look at. So we want to look
at Santa Tracker. And as you know, the Linux
from the system point of view, every process or every
thread, your name can be no longer
than 15 characters. So that's why this
one is actually the santatracker.debug. For some reason, the system
thinks this is the name. So if you select this one, you
are going to import this trace. And here, you can see
from the actual device, most of the frames
will be in gray. That indicates that they are
under 16 millisecond threshold. And so6e of the
frames are in the red because they are
over that threshold. You may wonder, how does
the profiler know how long these phases are taking? That is from the tracing point. So Android platform
engineers have added building tracing points
into some of the critical tasks in the Android system. So the example here is, if you
click the main thread here, these events are showing
from the trace events here. So if I zoo6 in here,
you will see this. These are trace event called
Choreographer doFra6e. So that's the first phase
we just talked about in the UI rendering. It happens in the main thread. If you click the
render thread, you can see there is an
event called drawFra6e. So that's the second phase. And you can also
see there are other tracing points in the system. They are all building syste6s. So they are available
on any Android phones, because they are building. And they are very useful to
get the timing information for some specific tasks. And actually you can have
your own trace points, too. And I'm going to demo that here. So today-- so here,
if we go into the top of the app, come back to
this view, we have the cloud. We have the snowflakes. I want to know exactly how long
6y code is spent joining them. So for the cloud, I go
to the Village View. So there should be
a onDraw method. So at the beginning
of the method, I add the instrumentation. The trace beginSection, you
need to provide a string, which is the section's name. At the end of the method, I
will end this trace event. For the snowflakes, it
goes to the Snowflake View. Again, it should be
in the onDraw method. I want to point out
the section name. You should pick
a name that 6akes sense to you so you
can recognize when you7re doing the CPU recording. So here we are doing the
Snowflake Android Dev Su66it 2018. So that makes sense
for today's demo. So now I have added the
manual instrumentation. So I will rebuild this
app, and re-profile it. So while we are waiting for the
build, you may be wondering, if I want to know how long
the two methods are taking, why don't you use the
trace Java method, that type of CPU recording you
are talking about a moment ago? So I would say trace Java
method is very powerful. It's very easy to use. But it has significant overhead,
because the Java Virtual Machine is collecting data every
time when the execution enters a method, and every time when
the execution exits a method. So if you have a lot of
frequent small method calls, that overhead can quickly add
up and become very expensive. If you use the 6anual
instrumentation, for example like
using the trace API, you have the full control of
when and where to collect data. So if you use that wisely, the
overhead will be much smaller. And as a result, the
data you collected will be more accurate. So let's verify the
instrumentation we have. So you go to the CPU Profiler. We collect a trace of
trace system calls. And here, we zoo6 in. We see this event again,
choreographer doFra6e. This is from the main thread
that is responsible for all UI elements. And if you keep
zoo6ing, you can see this is the frame we just
added, Village View, who is joining the cloud. And this is the
Snowflake View, ATS 2018. That's the thing responsible
for joining the snowflakes. So this is how to use the trace
system calls CPU recording. So you may have heard of a very
similar tool called systrace. So actually my Google
co-worker [INAUDIBLE] gave a lightning talk
yesterday on the systrace. So it's an extremely
powerful tool, but the learning
curve of the systrace is a little bit steeper than
the Android Studio Profiler. So you may choose the tools
that best suits your needs. OK, that's for the
trace CPU recording. I will hand over the
demo back to Dave. DAVID HERMAN: All right. Let's leave the CPU Profiler
behind, and jump over to the Memory Profiler. First of all, I want to
draw everyone's attention to this allocation
tracking pull-down. Some quick history here,
in Android Studio 3.0, when targeting Android devices
with O or newer, what we did is we would collect a call stack
for every single allocation that your app made. We did it because it would
be very convenient to use to have that history. However, some of our users
reported that profilers were slowing their app down. After we investigated, it
turned out to be this feature. So starting in
Android Studio 3.3, we now give you the
option to configure this. Let me go ahead and look here. So we have None,
Sampled and Full. None disables the
feature, Full enables it, and Sampled attempts to collect
a subset of the allocations. That sort of gives you a general
look for how your app7s memory behavior is, while not actually
affecting your performance as much as a full one does. Now that being said, whether
or not Full affects your app or not can depend
on the host machine you're running
on, whether you're targeting a device or an
emulator, or even your code. If your app code has a
lot of small allocations in it like Santa Tracker
does, it can be slow. But I recommend playing
around with the features. I'm going to go ahead
here and turn Full on. I76 going to go live here. So you notice once
I turned on Full, this allocation indicator
started showing up. I want some interesting
things to happen. So I'll just go ahead
and rotate the device. I may grab you there. Rotate it back. move it back over there. So now let's go ahead
and take a look here. So all I need to do at any
point is just drag across, and I'm going to
be able to see all of the allocations
and the deallocations that happened during that range. This could be a
really lightweight way to sort of get a quick look
at what your memory is doing. There's an allocation
and deallocation count. So sometimes you might be able
to find memory leaks even just doing this. And then the other nice thing
is maybe if you have this on, and you're doing
some other stuff, you can actually go back
in time and select a range, and still see what's
going in your memory. Now I'm going to go ahead here
and turn it back off again. And you'll notice that
the allocation indicated that it stopped tracking
by doing an empty circle. If I did Sampled Mode, it
would put a half-filled circle. Now looking at these
objects, once I click on it, you can see every single one
that's currently allocated, as well as where
it was allocated. This is very useful to get
your handle on a codebase. You can click around this and
see where different objects are coming from. That being said,
it might not help you understand why a
memory leak happened, what is holding on to my memory. If you want to get
that information, you have to go over
here to this icon-- this is the heap dump icon-- and click the heap dump. So what I'm actually going to
do here, just to let you know, I could not find a leaking
activity or fragment in the Santa Tracker app. So, full disclosure,
I added one. And what I did is I
added a memory leak to this Penguin Swim game here. So actually let's go ahead
and go into the activity, and leave it. And let me actually
do another heap dump. All right, so we're about
to get a bunch of heap dump information here. So I know that the fragment
inside this activity is called swim fragment. So just like CPU, we have
the Filter button here. And I can filter out all
of the different objects, and find that, yep, sure
enough, this swimming fragment is still alive. And I'll click on it, I'll
select the active instance. There is a lot going
on here, so I'm going to take a moment to
sort of explain a little bit. And let's just take a step back
and take a moment to absorb it. So I really want to explain
what this depth idea is. So imagine all your
Java memory that can be cleaned up by the garbage
collector lives in this heap. There are some special
Java objects that live outside of this circle. Those are called GC roots. When you create a new
thread, that basically creates this special GC root. Or static variables are
an instance of a GC root. So what ends up happening
is you might, in a thread, create an instance of some
class that it, in turn, creates an instance of
another class, and it, in turn, so on and so forth. And so basically
what you're doing is you're creating this
long chain of objects, and each item in that chain
has further and further depth from the GC root. Now you may have heard that
cycles are not a problem to the garbage collector. I can point at you, and
then you point at you, and you point back at me. But if we can't all
be reached, that will all get removed by
the garbage collector. So one of the things to note
is when you take a heap dump, you might see a scary
amount of things holding on to my instance, but
a lot of those are harmless. A lot of them are
potentially just cycles. And one way that you can kind
of know-- it's not always true, but it's a good heuristic-- is if the depth is greater
than your own depth. So let me explain why that is. So imagine that the garbage
collection root points to you. You create a child instance. And one of the things you do is
you give it your this pointer. So it's pointing back at you. That's a very common thing. Here, you point back at me. You're my child, but you
know who your parent is. So that means my
depth is then going to be added onto that
this pointer's depth. So that's a reason that
some of these depths here may indicate that
even though you're seeing that it's not an
issue, you'll also notice here that there's a few of these
items in the depth column where the depth is blank. That means that
there's actually no way to reach these at the moment
from any garbage collection root. So it will eventually
be cleaned up. Even though it's showing up
in the heap dump right now, you don't have to
worry about it. Just ignore it. It's as good as reclaimed. Just to make sure we
understand this a little bit, with a concrete
example, I'm actually going to look here at
this code, which is not the cause of our memory leak. Our depth is 1. This item's depth is 2. So what this is
saying is somewhere there is an instance
of a score view class. And that score view class
has a variable called share ClickListener,
whose value is me. So let's go ahead and take
a look at swimming fragment. And I'm going to search
for getScore view. So before, when I saw
this, I was trying to look through the code. I said, there's
gotta be some way that I'm going to get a
score view that points at me. And there's this
getScore view method inside my swimming fragment. If I jump to it, what
you're going to see is, yep, we create one, and we
pass in the this pointer. So that's sort of explaining
why there is a cycle there. I don't have to worry
about it, but at least it sort of explains why it's
showing up in the heap dump. Now you'll also notice there's
a lot of this$0 symbols. What's going on here-- because you're going
to see it a lot. You all know what
the this pointer is, but if you are an inner class--
if you're a nested class and you need to have access
to your outer classes' fields, the way that it
works is the compiler generates a synthetic variable. And instead of calling it
this, because that's taken, it calls it this$0. So this$0 means the this that's
one level out of my scope. And any time you create
an inner class that has a reference to
its outer class, or you create a closure, an
anonymous class instance, it's going to have that. So if you're all using
lambdas in your code, or anonymous
classes, you're going to see a lot of these zeros. This one has a depth of 0,
which means it's a GC root. That makes it really suspicious. So let's jump to the source. Now hopefully I
might look at this and say, oh, I wrote
this long-running task. As you can see, it's
not a static class. It's a final class. So if it's not static, it's
going to hold onto a reference to the parent class. That's what's going on here. And I might say, oh,
this long-running task, I created it, but I
forgot to cancel it. So let's go ahead
and take a look here. Yep, sure enough, I
left it commented out for no good reason. Let me uncomment it there. And let's relaunch. So what we're going
to do now is-- let me just make
sure it's saved. There we go. What we're going to do
now is reboot it just to make sure that in fact this
swimming fragment was released. One of the things
to keep an eye out for when you're hunting
for memory leaks is static variables
or singleton classes that are holding onto your
class or registering yourself with a listener, but
forgetting to remove it, or any of these inner
classes that for some reason may not end up stopping. And they're still running,
even though the activity is trying to exit. Are we profiling here? Yes, we are still going. And then another thing
I want to mention is it's not always
going to be this easy. You're not always going to
have this obvious culprit. So in that case,
what you're going to want to do is,
I would say, just try to get to know the code. Look for those things
that I talked about. Let's go ahead and enter. And we'll exit
here while I talk. And if you've cleaned it up,
even if you didn't find it through the heap
dump, the heap dump is still going to be
the source of truth. It's still going to
be the thing that guarantees to you that your
memory is actually reclaimed. So I'm going to go
ahead here, go back into the memory profiler. I'm going to click on
the garbage collector. So you may have noticed these
garbage collected events, automatically at the bottom. So for example,
there's a lot there. That's when the
garbage collector decided to collect on its own. But you can also click the
garbage collection button to manually cause it to get run. Now we still see
swimming fragment here. Let's see if the
depth is actually-- so as you can see,
swimming fragment is still showing up, which is-- I could be nervous. What happened? But the depth column
here is blank. Everything is just
basically waiting to be picked up by
the garbage collector. You can press the garbage
collector a few more times. One trick I like to do
is rotate the phone, and then rotate it again. Anytime you rotate an Android
phone, lots of fun things happen. It tells the garbage
collector things are going on. So in that case, let's just
do another heap dump here, and we'll see if the
swimming fragment is truly well and collected. I wish this came
with a drum roll. And there you go. It's gone. So the final thing I want
to quickly talk about is, if you're looking
at the heap dump, you're not always necessarily
hunting a memory leak. You might not know some
major class to look out for. So there's this concept of
shallow size and retained size. So shallow size is the size of
a single instance of some class that's been allocated. And retained size is all of
the things that's holding onto. So what you may
want to do is hunt-- sort your shallow size,
sort your retained size, maybe investigate
to see if there's any sort of suspicious
memory things there, or maybe you could
clean up your design and remove some
memory there earlier. Anyway, that ends our demo. Hand it back to Shukang. Let's go to the slides. SHUKANG ZHOU: OK, cool. So to recap, in
today's demo we have shown to use CPU
Profiler and Memory Profiler to get a better
understanding of the codebase of the Santa Tracker app. And to be honest, Dave and
I don't know much about it before we are preparing
for this talk. And we also have shown
that we don't only use profilers to diagnose
performance issues. We also use the profilers
to help us understand the performance of this app. As we said before, there
are also network and energy profilers in Android Studio. But unfortunately, we don't
have time to cover them today. So please refer to our
online documents and talks to learn more. So we hope you have learned some
tricks and tips from our demo today. We hope they are useful
when you approach your own codebase with your
Studio Profilers at your site. Thank you very much
for attending our talk. [APPLAUSE] [MUSIC PLAYING]