Tip:
Highlight text to annotate it
X
>> BRAY: Welcome to the last of the Android presentations for Google Developer Day 2010
in Brazil. So this is the last time I'll speak to you until next year, unless you come and
join us for a beer afterwards. Today, we are going to talk about high-performance Android
apps. As I mentioned during my last talk, the performance of an Android application
is very, very important to the quality of the user experience. And that's good but unfortunately
you can't always make things happen fast. To start with, you know, Android phones are
computers but they're kind of slow computers. And furthermore, Android phones are most interesting
when they are doing things that are in contact with the Net. And of course, whenever you
are doing something that involves the Net you are running at the speed of the Net. So,
first of all, you have to make the user's experience very fast. And secondly, you are
running on a slow computer that's talking to the Net. So clearly, you have some conflicts
there. So we're going to talk about two things. You know, how do you make this thing run fast?
But even more important, when you have to do something that's slow, how do you keep
the user's life good, okay? Now, this presentation should have a little bit of time for questions
at the end but we're starting late so--oh, well. So, when you listen to the Google engineers,
and particularly the Chrome team talk, they have this word called "jank" that they invented.
And jank is when it doesn't quite run--keep up with your finger when you're moving on
the screen. And when something feels slow they'll say, "Well, that's a janky app." And
you really have to work hard to avoid this because users are very sensitive to applications
that aren't responsive to them. And so what that means is you have to get out of the way
of the user Intervoice base code and let it do its job. And if you don't do that, bad
things happen. In particular on Android, every application has a special thread that is concerned
with doing the UI of the application. This is unfortunately called the "main thread"
and it creates the erroneous expectation from some people that they should do some--their
main work on the main thread. Well, they shouldn't. That thread belongs to the UI and they should
get out of the way. And if you do the wrong thing, this will happen to you. Users hate
this. You should hate this, too. In the developer relations group at Android at Google, we get
a lot of applications for review. And we get them for review because they come from important
partners or because we hear good things about them and especially because we are looking
for good applications to feature in Android market. And if an--if we evaluate an application
and this happens once, that application is gone. It's just not interesting. It's an unacceptable
situation. So, why does that happen? Basically, when you tap on the screen and the system
calls your application and it doesn't return in five seconds, you're dead. If you have
a broadcast receiver that gets an event and it doesn't finish in 10 seconds, you're dead.
And when that happens, it doesn't just stop you, it destroys your task, clears you from
memory, finishes you off; you're really dead. So the computers are really, you know, a billion--it's
a gigahertz processor in there so really, you shouldn't really have to do anything,
while the user is waiting, that will take five or ten seconds. But, you know, it's actually
much worse than that because users complain about delays that are much shorter than five
seconds. So, let's give you some numbers here and the context is--the red text at the top
is very important. For video to look really good, you know, 60 frames a second, so each
frame is 16 mega--milliseconds. So if you are computing your video frames, you have
16 milliseconds, no longer, to compute each frame. And if you actually measure people
and find out, you know, how short a delay can they measure, it's 100 to 200 milliseconds.
So if it's over a fifth of a second delay they will see it every time. If it's less
than a tenth of a second possibly they won't notice it, okay? So if you look at some of
the--some--this is measured on a Nexus One, which is still a pretty fast Android phone.
So if you look at those first two bullet points, it's obvious that sending interprocess messages
back and forth is very fast, so you shouldn't worry about that. If, you know, if you need
to send a message to another process to get it to do something and get it come back to
you, that's safe, that's good practice. What happens is as soon as you get into I/O, things
start to hurt. So, just to read one byte off the flash storage, 5 to 25 milliseconds. So,
that means you can't even read one byte and compute a frame of video. To write Flash,
it's horrible. It, you know, can be between 5 and 200 milliseconds to write into the storage
and it's variable. It varies according to a bunch of things and it's complex and unpredictable.
And if you want to send a simple ping over a 3G network, you're really casting your fate
out into the hands of luck. You know, it can be anywhere from a tenth of a second to, you
know, most of a second just to ping. And it depends on how strong the signal is, it depends
where you are, it depends how busy the network is, it depends on all sorts of things that
you don't control. So basically if you're going to do anything over the network, you
know, you're really, really looking for some delay. And, of course, what is the thing that
we do most often over the network? The most single, most common thing we do is to go do
an HTTP request to get something from a web server. And, you know, it's really rare that,
that executes in under one second. I mean, if you happen to be going to Google to get
the Google front page, you know, and you're near an edge server, maybe. But typically
you should count on the best part of 10 seconds, you know, to just get a simple, you know,
6K over 3G networking. It's a little better if you're on Wi-Fi, but still, it's tough
to see anything under one second, okay? So, that's the bad news and it gets even worse.
So, here is a little disk benchmark that--it does much like a--similar to what happens
when you update a SQLite database. So it creates a file, writes half a K and deletes it and
so on. And the flash storage that we have on these devices is not like the hard disks
that you are used to in your laptop computers and your big servers. It--the performance
depends on how full the disc is as you can see from here. Weirdly enough, it also depends
on how old the Flash storage is because it gets slower as it gets older. It get--it depends
on a bunch of other factors that you just don't control. So, you really have very little
way to predict how long writing data to persistent storage is. Now, it turns out that in Google
up through Froyo--I'm sorry, Android up through Froyo, we're using the yaffs file system which
has some advantages and some other disadvantages. And we're doing actually a lot of work to
improve this in future releases of Android but still, it's, you know, this is not high-performance
storage and you just have to deal with that. And many people who do any kind of storage
operations in their applications use SQLite to store all their data. And that's fine.
Except for just like any relational database, any time you update it, that's expensive.
And it's very similar to the situation you find in your relational databases in your
big business applications, retrieving data from the database can be slow so you can put
an index in there and that will make it very fast. But then, that--when you update it,
it has to update the index and that's slow. So it's a tradeoff between read performance
and write performance, it's the same old one. And you got to watch out because sometimes
the most amazingly simple-looking SQLite queries can actually lead to a lot of complicated
work. So, SQLite has a thing where you can say, Explain--what's it called, Explain and
Explain Query Plan. So, you can give a query and SQLite will tell you how it plans to execute
it and you can--I advice doing that, you know, especially if you're having performance problems,
have a check and see what SQL's really doing. Now, one thing we see a lot of that is a really
bad idea is many, many applications keep log files. And if we see a lot of people putting
a log file in a SQLite database, and that's really expensive and there's no good reason
to do it. I mean, after all, Android is Linux and Linux has a perfectly good file system,
really quite a very good file system, in fact. And so, why wouldn't you just do a write onto
the file system for your logging? I mean, what do you need a database for your file
system for, for your log file for? So to, you know, stay away from SQLite when you can
and when you can't, use it very, very carefully. Now, the lesson from this is obvious is that,
you know, I/O hurts. The good news is that, you know, computation is actually surprisingly
fast. You know, because we're all used to these very fast processors and things like
this you think, "Oh, it's got a one gigahertz processor. That's not fast." Well, you know,
I'm an old guy and I can--you know, one gigahertz processors are really fast. You can do an
amazing amount of computation in there. So caching things, you know, in memory and saving
I/O by doing computation is almost always a good idea. And so, you just have to assume
that anytime you do I/O something bad is going to happen. And like I said, that's where bad
reviews come from, that's where bad ratings come from, and that's how you never get featured,
is if you do I/O in a stupid way. So the only answer is to do threads, to split the work
off into another thread. If you do anything on the I/O thread, you're just really asking
for trouble. So what you really need to do is you need to fire up another thread, pass
it the workload and have it update you when you're doing that. Now, in the example I showed
in the first talk, we're going to be looking that again. I did all that stuff by hand.
I manually created a thread and it passed back and stepped to the main thread to update
the UI. And that was kind of clumsy, and that was because I didn't know about this thing.
So there's something called asyncTask. And you know, I forgot to ask at the very beginning,
a question I always ask, "How many of are actual Android developers?" Oh, wow. So a
lot of you aren't. And for you--to those who aren't, I apologize, some of this will go
over our heads but, oh well, okay. So asyncTask is basically a wrapper for sending something
to execute in a background thread. And as you know, since it's the same semantics as
Java.lang.thread, setting up threads and running them and controlling them is kind of awkward,
and difficult, and tricky. And asyncTask is a very useful tool to get around that. So
I'm going to show you an example of a class here that is downloading some files. Now,
I suppose the people at the back have a hard time seeing the code, I'm sorry. If you really
want to see the code, there's people sitting on the floor here at the front so that's a
good option. So what this thing does is it downloads some files, okay? Now, downloading
a file from the network and storing it on your computer--on your phone is something
that's guaranteed to be slow, right, and uncontrollable. So what it does is it, you know, for--and
I'll zero up the count, it downloads a file and then it calls publish progress. So the
way that asyncTask works is that you see how you start it down at the bottom? You make
a new download files task. So asyncTask is an abstract class so you have to extend it.
So you make a new one of those things and when you call the execute method, it goes
and calls the doing background method and it takes care of setting up the other thread
and starting it and all that stuff for you. So when you call the publish progress method,
that goes and calls the on progress update method but it calls it in the UI thread, okay?
So it takes care of maneuvering things back and forth between the threads. So in the on
progress update method, he is just--I don't know, updating a progress bar or, you know,
showing a number or something to the user on the screen. And to do that, you have to
be in the UI thread. So getting the messages back and forth between the threads can be
tricky and this just does all that. It's also got some other handy methods. It's gotten
on pre-execute and in on post-execute and so on so it takes care of all the book-keeping
for you and I totally recommend it. You know, to do that, using naked thread messages and
so on, you probably will be looking at three or four screen-full of code with lots of chances
to make silly errors and this just takes care of it. Was there anything else I was going
to say? No. So this is very handy. Now, in this particular example, he's doing something
that the user probably wants to be informed about. You know, the user said, download these
files, so the user's willing to wait, will it download? So, we're going up in the bound
and informing him, and if he loses his patience and just, you know, hits the home key to go
to some other application, that's fine, it will keep running in the background and everything
will be just fine. But there's some cases where you don't really care about what's happening.
So this is what's called "fire and forget" mode and this is from the Android Browser.
Now, whenever I go to a new page in the browser it has to update my browsing history so that
I can use, you know, the back button and things like that. But the user doesn't care. The
user doesn't want to be informed that I'm updating the browser history, that's just
a complete background task. So in this case, he is not doing any progress updating or anything,
he's just, you know, making a new asyncTask right there on the fly and calling its doing
background method and there you go. So we call that fire and forget mode. It's off in
the background. I don't care about it, just do it, finish it, come back. So that works
just fine but it's got one problem. Now, let me just talk a little bit more about asyncTask,
yeah. So there are few things you got to be careful about with asyncTask. You can't call
it recursively, right? You can't launch an asyncTask and then have that asyncTask go
and launch another asyncTask. So basically it has to come from a UI thread. And it's
perfectly possible that if you launch an asyncTask you're activity could go away and you could
be killed. The other reasoning is that, you know, he's doing something else, you're still
running in the background, the system runs short of memory, it'll just go kill your thread.
And if it's doing something important you know that's bad. You don't want that to happen.
So it turns out that if you want to run something in the background and you want to tell the
system that it needs to be alive, that's what services are for. And once again, just like
activities and threads, services take a certain amount of work to set up and run. So there's
this thing called IntentService that sort of does the same thing. It launches a service
in the background and you just start the service and it goes off and you send in intents and
it runs the intents. And since it's a service, the system will try really hard not to kill
it. Now in practical terms, in all the Android programming I've done, which is a certain--not
a huge amount, I do other things as well but I've done quite a bit, I've never ever seen
a service get killed. I know it can happen in principle but I've never actually seen
it--seen that happened so it's really pretty safe. So here's an--a good example of that.
So this is the calendar application on the system. So, I don't know about you but you
know, sometimes I pull out my phone after I've been talking to somebody for a while
and it's got a calendar notification in the notice bar. So I pull that down, it will tell
me about three things that, you know, I either should have gone to or I already went to or
I'm going to go to. And so you just say, "dismiss all," right? So when it hits--when you hit
the dismiss all button, the calendar has to go and acknowledge that you've seen all of
those things and mark them as being in the past and so on, and actually it has to do
quite a lot of database clean-up. And the user doesn't care. The user doesn't want to
wait for that. The user doesn't want to hear about it. You just want it to happen. But
you ought to make sure it really does happen because you don't, you know, you don't want
it coming back to bother the user again. So what he does is he goes and makes a--extends
intentService, and then he goes and, you know, gets the resolver and goes and fix this up
and it takes care of all the calendar stuff. And then when you--when this thing happens,
you make a new intent to point it directly at that class and then you just say, go and
start that service and away it goes in the background and the user's off doing something
else, okay? So start services is really, really good. Start services is also very good for
use in a broadcast receiver. So you get a broadcast receiver, you know, like something
needs to happen in the background, you can't spend much time, you start the service, you
go away. So we've talked about, you know, getting things off of the main UI loop, in
order to that the user doesn't have to wait for them. And just to make it a 100% clear,
the reason we're doing this, like for example in that application where he was downloading
files, so he was willing to wait for downloading the files. So to be polite to him, you want
to show a progress, you know, that, "Hey, I'm working and downloading those files for
you." But if you actually were doing the downloading of the files in the UI thread, and the guy
does something like tap the menu key or something like that, well, it won't respond because
it's busy downloading the file. So the user will see a frozen phone. That's why you have
to do these things, okay? So the question is--so this also gets us to some very interesting
user interface design. So, here's a sketch of how you might design the user interface
for something that is slow and going to be relatively time-consuming. So the first thing
is, if you're doing something time-consuming and you don't want the user pushing any buttons
in your app or something like that, disable them first. You know, before you actually
start doing any work, turn off all the buttons that you don't want the user pushing. Then
if it's just going to be a really, really quick job, there's a way. They run a little
spinner, a little progress dialogue, right there in the status bar to show that it's
working. And if it's going to be something that's only going to be, you know, a fraction
of a second or a second maybe, go ahead and just spin there. And so what they're--in this
strategy, what they're saying is, first of all you turn off the UI, and then you show
a little spinner and if it really goes on for a while, you set an alarm to go off in,
you know, a fifth of a second or something, and if you're still working, well then, you
know, put up a progress dialogue of some sort to show the users that you're working. And,
of course, the work is always has to be happening off in another thread. So designing the user
interface for these kinds of slow activities is actually a tricky thing.
So some of the general lessons that apply to application performance are what we just
spent a few minutes talking about, it's, you know, the network and storage UI are really
painful and you have to be careful about those. So, let's assume you all ready know this.
Watch out for excessive memory allocation. In particular, a very rapid burst of a whole
bunch of new calls of small object allocations tends to be fairly expensive and to cause
the garbage collector to burn some CPU later so, you know, it's a good idea to batch up
your allocations if you can. A very common technique in Java, particularly among fairly
advanced Java programmers, is reflection. You get an abstract object, you know, it's
just an object of some sort. You look inside it to see what methods it has and you call
those methods. This is very, very useful when you're dealing with for example, databases
and you, you know, you get a database object. You can look inside it and see what couples
it has and things like that. It turns out that at the moment in Android, this Java technique
is called reflection, Android reflection is extremely painful and extremely slow. We are
talking 100 times as slow as typical native code. So it will really, really slow down
your applications. Now, it's a common technique to use, reflection, if you want to have an
application that runs on both 1.5, 1.6, 2.0 and 2.2 of Android, it's very, very common
to say, "Oh, well let me see. Do I have this method? If I have this method then I know
I'm running on 2.2 and I can do this extra fancy technique. Otherwise, I'll do it the
simple way." And that's perfectly okay. We recommend doing that in fact, that kind of
reflection. But that--but you only want to do that kind of thing when you're at the very
beginning of your application, just getting started, right? You don't want to do it in
the middle of a tight loop or you'll just totally kill your performance. There's a whole
section in developers at Andriod.com about designing for performance and it's worth reading.
There's some things in there that are kind of surprising. You get a surprising benefit
for making things static when you can and so on. Floating point math is a problem in
any system and it's also a problem. So now, I'm going to do a demo to show you two things,
what a performance-sensitive UI might look like and then what it does. So, this is a
little application I wrote and when you get a new Android phone--put it back for a sec,
please. So, can I have the computer for one more minute? Yeah, thanks. So, this app is
for when you get a new Android phone. When you get a new Android phone as you know, you're
email addresses, your contacts, your schedules, all that kind of stuff comes over to your
new phone. But the history of your phone calls doesn't. They are your call log and your history
of your SMS' and I hated that because there's a lot of useful information in there. So I
wrote this little application called LifeSaver that takes the SMS messages of you--out of
your log and all the phone number calls you've made and puts in on the SD card. You move
the SD card into a new computer, you run it, it copies them back and then it saves your
life and brings it back for you. Okay, so let me show you that app. Can we have this
thing now, please? So, I'm running it on my Nexus One here. So, can we see that? Yes,
I see that. So, here's the LifeSaver app--okay. So, what it's going to do is it's going to--oh,
I'm sorry. Thank you. What it's going to do is it's going to read all the phone calls
out of the phone call log and all the SMS' out of the SMS and it's going to save them
onto the SD card. So, there's a lot of I/O in this. So, it says, "Oh, where do you want
it," once. "There's like a life here, can I write it," so. So, immediately I send these
things away and I draw a progress bar. Well, that's working. Now, there's only a couple
of 100 calls on here. So, we showed the users something to look at to keep them busy while
it was working. Now, this simply--if this was a teenager's phone, it would take like
two hours to run. But, you know, but for me it's okay. And that's not bad, but it seems
to me that that is actually a little bit slow. And for it wasn't--actually I do have a couple
of complaints in my comments on, you know, what they'd be saying, "Gee, it took a long
time." Frankly, I don't worry too much because it only runs once, you know, once on the old
phone and once on the new phone and then it's done. But, you know, I would like it to run
faster. So, can we come back to the slides now, please? Oh, drat, I forgot something
here. One moment. Oh, well, we can work around that. So, I want that to run faster. So now
I'm going to consider how would I make that app run faster. So, I have to think, "Well,
why is it slow?" So, here's the bad news. If you're going to write an app and it's reasonably
complex, you're not smart enough to predict where the performance bottlenecks are. Nobody
is smart enough. Trying to optimize your program in advance, it will waste your time and it'll
probably make your program bad. And I noticed that a lot of young programmers. In particular,
young male programmers are, you know, really like to think that they're super smart and
they're, "Oh, I know that's slow because it's this and I'm going to put a hashtable in there."
And they spend three weeks coding up a hashtable and then it doesn't run any faster. So, you
just can't guess. This is the right way to do it. So, the right approach is that, you
know, simple programs are better than complex ones. So you should write your app in the
simplest way that could possibly work. Now, of course, that doesn't include doing any
heavy work on the UI thread. Maybe it'll, you know, maybe it's fast enough then. You're
done. Go have a beer. And if it's slow, then you have to measure. You have to get a profiler
and you have to measure to find out why your program is slow before you start to fix it,
okay? So, Android app two Froyo has a decent profiling tool called Traceview. And the way--and
we'll talk about that in a second but, you know, you can do a lot of profiling just by
putting log statements with time stamps and time stamps and to find out what's going on.
What we do at Google is we get all the Googlers to run code and we run profilers on all of
them at once and collect the data just statistically. That's a very powerful profiling technique.
But--so, let's talk about Traceview because it's sort of the default profiler. So the
way it works is you can start and stop it from adb, but I find it actually better to
put code in to do it. So, let's actually do that and I'll show you how it works. I need
to type one in here. So what I'm what going do is you put it in a--here's that actual
code that does that. So I would put it--I'm going to put in a start tracing and a stop
tracing call. >> Hey, Tim, we can't see the presentation
right now. >> BRAY: Okay. So, you couldn't call it the...
>> You forgot your mic. >> BRAY: Now, what I would normally do, we're
a little short on time, so I actually--so what--the way that you would normally is you
would put those calls in, send it over, run it and then it will write a trace file. Now,
that call I put it, I said, you know, android.SD.debug, they'll start method tracing LSD for LifeSaver
Debug and it'll create a file called /SDcard/LSD.trace and then you pull that over onto the--onto
your computer and you run the program called Traceview. Since we're a little bit short
of time, I'm going to actually skip doing that and just go and run Traceview.
So, what I--so one thing you would have observed if I'd actually run that is that be--when
turned the method tracing on, it all runs a lot slower. So here you see the display
that Traceview makes and you can see that here is the main thread, where it's doing
the UI. There's my thread where I'm doing the reading and writing. And then you can
look at these things for example and you can see that that's the garbage collection, that
purple stripe, okay? So, let's zero in on just, you know, 100 milliseconds or so of
this. We're even closer. And the colors there correspond to the colors in the display below.
Now the really interesting part here is the display below. If I go down here and look
I can see that two-thirds of the time of this thing is being spent inside print line. Oh,
now if I had looked that up and asked myself, "Why is that slow," my first two theories
would have been, "Well, it's slow reading the telephone logs out of the system or it's
slow writing the data of the SD card," right? So this suggests that the problem is the writing
the data to the SD card so let's just drill down on that. And we discover that of the
two-thirds of the time it spends in print line, half the time is actually doing print
line. And if you look at that, it's spending half of it's time doing new lines. So, it
looks--if you look--drill down further on that, it becomes fairly obvious that yeah,
they're both, you know, half of two-thirds of the time is actually the I/O time but the
other half seems to be in Java.lang.string.valueof, what? Oops, I put on the wrong thing at the
back here. And--what did I do wrong? I know, there it is. So there, that is, and in value
of 99% of that time is in jsonobject.tostring. So it--apparently 34% of my total time on
this program is in jsonobject.tostring. Now you can drill down on this but another thing
you could do is you could turn and just sort this stuff by the amount of time it spends
in the routine and you discover that these are all the things of over 1%. They are all
bits of json.tostring. So it turns out that I've discovered the fact that this particular
Android implementation, the json.tostring, is incredibly inefficient and stupid and it's
slowing down my application. So I could go and try and optimize the I/O or I could just
stop using json. But the real lesson I want to teach you is, is that my intuition about
what was making the program run slow, my guess, was completely wrong. And your guess is going
to be completely wrong too, when you do it. So you need to use tools like Traceview to
make things like this work better. Now, I'm going to switch back to my presentation here
and talk a little bit about Traceview. Now you do have to worry about Traceview a little
bit. The picture it presents is not that great. To start with it, it turns that the JIT off
in the VM, so that really hurts your performance. Secondly, it overestimates the cost of calling
methods, because the--it turns off a bunch of optimizations there. So, you know, when
I say that this thing taking 35% of my time that doesn't mean, you know, between 34 and
36 that means, you know, well, you know, a noticeable chunk. So the numbers are not very
quantitatively reliable. But they are very useful actually in terms of teaching you what's
going on. In terms of futures, were actually doing in the future releases of the operating
system, were doing a lot of work on the screen manager, to make the screen management faster.
One thing we're talking about--Brad already talked about this at I/O so it's not a secret,
we have all talked about not doing I/O on the UI thread, right? Well, all of us are
smart now. We'll never do that again. But it turns out there's many simple-looking utility
calls in the system that end up doing some database I/O even if when you're not looking
and you don't know about that. So were introducing--were going to be introducing the thing before too
long where you can say I'm in "Strict" mode. And whenever--when you're in strict mode,
whenever you see--encounter any I/O on the UI thread, it will throw an exception. So
this will help you--and the amount of speed up it's brought, just to ordinary utilities
even some reroute is amazing. So that'll be nice. And also Traceview is much better than
nothing. But it's not that great so were going to be bringing in some better profiling tools
in the next release. So that's it. That was about all I was going to say. So just to recap,
the UI thread is sacred. Keep your I/O off the UI thread. Entertain the user, show them
something while you're doing something that you have to wait for. And to make your program
run fast don't guess, profile it, figure out what your problems are before you fix them.
We've got one or two minutes for questions. Lights up, please. Any questions? Yeah? Nope?
Okay, thank you all.