CHET HAASE: Hi.
I'm Chet Haase, an engineer in the Android Team at Google.
I work on graphics and animation stuff.
And today I wanted to talk about the topics of bitmaps.
In particular, I want to talk about bitmaps and garbage.
First, a history lesson might help.
Prior to the 3.0 release, we managed bitmap memory at the
native level.
The VM knew about it, but it really didn't manage it, and
you, in your application, were responsible for calling a
recycle to very actively manage that.
When you're done with bitmap, you call recycle.
That gets rid of the memory.
Otherwise, you may run out of enough native memory to
allocate another bitmap.
So this was a very careful thing that applications needed
to manage very tightly.
We decided that it would be a little bit better for everyone
if we actually managed that for you.
And we brought the bitmap memory back into the VM along
with the rest of the memory that the
garbage collector manages.
So now when you allocate memory for a bitmap, we
allocate it on the virtual machine heap.
And then we know when references to that memory are
gone so that we can collect it appropriately.
In particular, when a reference to a large bitmap
goes away and you want to allocate another bitmap, then
we can see that, well, there's no reference to
this other one there.
We can collect it in order to make room for the new one that
needs to come into the heap at that time.
So all of this is great.
Except that it gets into situations where maybe the GC
is going to be triggered more than it was before, because
you're not managing your own memory manually.
Instead we're managing that memory for you.
And we manage memory by having the garbage collector kick in
and collect things when it needs to.
If there's not a memory in the heap, we'll collect stuff that
needs to be collected, including large bitmaps.
Now the trick to this is that large amounts of memory can be
more expensive to collect.
And large heaps are more expensive to collect.
So if there's large pieces of memory due to bitmaps
constantly churning through the system, then we're going
to be collecting memory for them a lot, right?
If you're constantly, like on every frame--
I saw an application like this recently, where on every frame
of every animation they were loading a new bitmap and
getting rid of the old one.
And in the old system, they would actively manage this and
call recycle, and the other one would be loaded in place.
And life was good.
But as of 3.0, that meant that the old one was on the heap.
The new one wanted to be on the heap.
There wasn't enough room for the both of them.
And so it needed to collect from the first one.
Relatively expensive garbage collection to then load in the
second one.
Next frame around, same thing.
Needs to collect the old one to make enough
room for the new one.
Now that's a reality of dealing with memory management
and garbage collectors.
And it's decent enough.
But there's actually a really good way that you can work
around that problem.
And in particular, if you're working with bitmaps that are
exactly the same size, which this particular application
was, then you can actually reuse bitmaps.
So you can have one bitmap that everything gets loaded
into along the way and save yourself some serious garbage
collection and performance along the way.
So let's take a look at a quick demo.
I call this demo Bitmap Allocation.
And it's a picture viewer.
I have some pictures that I've loaded up in the background.
And we can click through these and see these other images.
And each one of these images is getting loaded on the fly
as we go through that just for the purposes of this demo.
And then I print out the number of milliseconds that it
took to load each of these images up at the top.
So these loads took 290, 326, 304, 340, 320.
So a little non-deterministic based on what else is going on
in the system.
And how long GCs took and stuff like that.
Now if we reuse the bitmap, then, again, this is a little
bit hard to see, because the numbers can be
a little bit erratic.
But what I see in runs that I've done, at least on other
devices, is a savings of, in general, about 10% to 20%,
which is not bad.
If you're talking about 300 milliseconds in a frame,
saving 30 milliseconds is a big deal.
Now ideally you'd actually find a better way to load
these images than loading them on the fly when you're ready
to draw them.
That's not the best way to go about it.
But the point of this demo is more to show the raw amount of
time that it might take to load these things.
And a lot of that is the garbage collection.
So let's take a quick look.
Before we look at the code we can take a look at a log that
I ran right before this where I was seeing what happened
before and after I checked that little box that said
Reuse the Bitmap.
And here's what we get.
Oh, let's see.
Here's what we got before I checked the box.
Is nothing.
This was the output.
Not the GC output above, but this output here is what we
saw when we reused the bitmap.
But when we're not reusing the bitmap, this is what we see.
On every frame we're incurring potentially very expensive
garbage collection.
Some of that runs in parallel.
But we still have pauses of 21 milliseconds for Alex, 3
milliseconds here, 12 milliseconds there.
If you imagine that a 60-frame per second application only
has 16 milliseconds in order to draw each frame, these
numbers are huge.
You really want to avoid them if you can.
So how do we do that?
We use Bitmap Factory Options.
So let's go in here.
Take a look at the code.
This is the bitmap application code.
And here we have the check box that determines whether we are
actually going to do reuse or not.
And in here we're going to create our bitmap that gets
reused on the fly.
So first of all we're going to decode the initial image that
we start with.
This application does assume that all the bitmaps
are the same size.
And this is a restriction with reusing the bitmaps with this
API, is that it will only work for bitmaps of the same size.
So we get the size of the initial bitmap.
And we get only the size.
We don't force it to actually load that bitmap yet by using
the inJust Decode Bounds Options.
And that will return the width and the height that we can
then use in our Create Bitmap Call.
So we use the width and the height that we need to sort of
get this sandbox we're going to load all these things into.
Then we reset that flag.
And we set the inBitmap Flag.
And that is the new API that was introduced in 3.0 at the
same time as the other change to the memory
management of bitmaps.
We set that equal to this sandbox bitmap
that we've just created.
And then we set the inSample Size to 1.
And this is a necessary and unfortunate bug that got fixed
in a recent release where if you didn't set the sample
size, it wouldn't do the right thing.
So you do need to set it to what would be the default
option, inSample Size of 1.
And then we decode the resource.
And away we go.
So now we're in our Click Listener.
Every time you click on the image, we're going to load in
the next image.
And we're either going to reuse that
bitmap or we're not.
So if the check box is checked, we're going to reuse
the bitmap.
And we're going to do that by saying the bitmap options
equals these options that we've created above, which has
this inBitmap Parameter set in it, which tells--
it's a trigger to the code to say, OK, this is the sandbox
where I'm going to actually load this new bitmap into.
And so we set the inBitmap Flag there.
And then I was just issuing some timing, because this is
how I produce the times that are spit out in the UI so we
can see how long these things take.
We then decode the resource, and that will either reuse the
bitmap or not, depending on whether we set the bitmap
parameter above.
And then we set the new image bitmap in the image view.
Finally, we get the final time that it took and
display that in the UI.
And that's it.
Not very hard to use, but definitely worth using.
Especially if you're in the case where you're constantly
loading in new bitmaps and those bitmaps are the same
size as other bitmaps that you're now done with.
This basically allows a garbage collector get out of
the way, because there's nothing to collect if you're
loading the new bitmap into the place of the old one.
So check out bitmap options, in particular inBitmap.
And save some grief over garbage collection times.
Thanks.