The following is the first few sections of a chapter from The Busy Coder's Guide to Android Development, plus headings for the remaining major sections, to give you an idea about the content of the chapter.
Knowing that you have CPU-related issues in your app is one thing — doing something about it is the next challenge. In some respects, tuning an Android application is a “one-off” job, tied to the particulars of the application and what it is trying to accomplish. That being said, this chapter will outline some general-purpose ways of boosting performance that may counter issues that you are running into.
Understanding this chapter requires that you have read the core chapters and understand how Android apps are set up and operate. Reading the introductory chapter to this trail is also a good idea.
One class of CPU-related problems come from purely sluggish code. These are the sorts of things you will see in Traceview, for example – methods or branches of code that seem to take an inordinately long time. These are also some of the most difficult to have general solutions for, as often times it comes down to what the application is trying to accomplish. However, the following sections provide suggestions for consuming fewer CPU instructions while getting the same work done.
These are presented in no particular order.
Most of your algorithm fixes will be standard Java optimizations, no different than have been used by Java projects over the past decade and change. This section outlines a few of them. For more, consider reading Effective Java by Joshua Bloch or Java Performance Tuning by Jack Shirazi.
Few objects in
java.* namespaces are intrinsically thread-safe,
java.util.concurrent. Typically, you need to perform
your own synchronization if multiple threads will be accessing
non-thread-safe objects. However, sometimes, Java classes have
synchronization that you neither expect nor need. Synchronization
adds unnecessary overhead.
The classic example here is
StringBuffer was part of Java from early on, and, for whatever
reason, was written to be thread-safe — two threads that append
to the buffer will not cause any problems. However, most of the time,
you are only using the
StringBuffer from one thread, meaning all
that synchronization overhead is a waste. Later on, Java added
StringBuilder, with the same basic set of methods as has
StringBuffer, but without the synchronization.
Similarly, in your own code, only synchronize where it is really
needed. Do not toss the
synchronized keyword around randomly, or
use concurrent collections that will only be used by one thread, etc.
The first generation of Android devices lacked a floating-point
coprocessor on the ARM CPU package. As a result, floating-point math
speed was atrocious. That is why the Google Maps add-on for Android
GeoPoint, with latitude and longitude in integer microdegrees,
rather than the standard Android
Location class, which uses Java
double variables holding decimal degrees.
While later Android devices do have floating-point coprocessor support, that does not mean that floating-point math is now as fast as integer math. If you find that your code is spending lots of time on floating-point calculations, consider whether a change in units would allow you to replace the floating-point calculations with integer equivalents. For example, microdegrees for latitude and longitude provide adequate granularity for most maps, yet allow Google Maps to do all of its calculations in integers.
Similarly, consider whether the full decimal accuracy of floating-point values is really needed. While it may be physically possible to perform distance calculations in meters with accuracy to a few decimal points, for example, in many cases the user will not need that degree of accuracy. If so, perhaps changing to fixed-point (integer) math can boost your performance.
Years upon years of work has gone into the implementation of various algorithms that underlie Java methods, like searching for substrings inside of strings.
Somewhat less work has gone into the implementation of the Apache Harmony versions of those methods, simply because the project is younger, and it is a modified version of the Harmony implementation that you will find in Android. While the core Android team has made many improvements to the original Harmony implementation, those improvements may be for optimizations that do not fit your needs (e.g., optimizing to reduce memory consumption at the expense of CPU time).
But beyond that, there are dozens of string-matching algorithms, some of which may be better for you depending on the string being searched and the string being searched for. Hence, you may wish to consider applying your own searching algorithm rather than relying on the built-in one, to boost performance. And, this same concept may hold for other algorithms as well (e.g., sorting).
Of course, this will also increase the complexity of your application, with long-term impacts in terms of maintenance cost. Hence, do not assume the built-in algorithms are the worst, either — optimize those algorithms that Traceview or logging suggest are where you are spending too much time.
An easy “win” is to add
android:hardwareAccelerated="true" to your
<application> element in the manifest. This toggles on hardware
acceleration for 2D graphics, including much of the stock widget
framework. For maximum backwards compatibility, this hardware
acceleration is off, but adding the aforementioned attribute will
enable it for all activities in your application.
Note that this is only available starting with Android 3.0. It is safe to have the attribute in the manifest for older Android devices, as they simply will ignore your request.
You also should test your application thoroughly after enabling
hardware acceleration, to make sure there are no unexpected issues.
For ordinary widget-based applications, you should encounter no
problems. Games or other applications that do their own drawing might
have issues. If you find that some of your code runs into problems,
you can override hardware acceleration on a per-activity basis by
<activity> elements in
Calling a method on an object in your own process is fairly inexpensive. The overhead of the method invocation is fairly minuscule, and so the time involved is simply however long it takes for that method to do its work.
Invoking behaviors in another process, via inter-process
communication (IPC), is considerably more expensive. Your request has
to be converted into a byte array (e.g., via
made available to the other process, converted back into
a regular request, then executed. This adds substantial CPU overhead.
There are three basic flavors of IPC in Android:
Using a remote service is fairly obvious when you do it — it is difficult to mistake copying the AIDL into your project and such. The proxy object generated from the AIDL converts all your method calls on the interface into IPC operations, and this is relatively expensive.
If you are exposing a service via AIDL, design your API to be coarse-grained. Do not require the client to make 1,000 method invocations to accomplish something that can be done in 1 via slightly more complex arguments and return values.
If you are consuming a remote service, try not to get into situations
where you have to make lots of calls in a tight loop, or per row of a
AdapterView, or anything else where the overhead may
For example, in the
sample project, you will find a pair of projects
implementing the same do-nothing method in equivalent services. One
uses AIDL and is bound to remotely from a separate client
application; the other is a local service in the client application
itself. The client then calls the do-nothing method 1 million times
for each of the two services. On average, on a Samsung Galaxy Tab
10.1, 1 million calls takes around 170 seconds for the remote
service, while it takes around 170 milliseconds for the local
service. Hence, the overhead of an individual remote method
invocation is small (~170 microseconds), but doing lots of them in a
loop, or as the user flings a
ListView, might become noticeable.
Using a content provider can be somewhat less obvious of a problem.
ContentResolver or a
the same whether it is your own content provider or someone else’s.
However, you know what content providers you wrote; anything else is
probably running in another process.
As with remote services, try to aggregate operations with remote content providers, such as:
bulkInsert()rather than lots of individual
delete()in a tight loop – instead, if the content provider supports it, use a more complex “WHERE clause” to update or delete everything at once
The content provider scenario is really a subset of the broader case where you request that Android do something for you and winds up performing IPC as part of that.
Sometimes, this is going to be obvious. If you are sending commands
to a third-party service via
startService(), by definition, this
will involve IPC, since the third-party service will run in a
third-party process. Try to avoid calling
startService() lots of
times in close succession.
However, there are plenty of cases that are less obvious:
sendBroadcast()involve IPC, as it is a separate OS process that does the real work
registerReceiver()) involves IPC
LocationManager, are really rich interfaces to an AIDL-defined remote service, and so most operations on these system services require IPC
Once again, your objective should be to minimize calls that involve
IPC, particularly where you are making those calls frequently in
close succession, such as in a loop. For example, frequently calling
getLastKnownLocation() will be expensive, as that involves IPC to a
The way that the Dalvik VM was implemented and operates is subtly different than a traditional Java VM. Therefore, there are some optimizations that are more important on Android than you might find in regular desktop or server Java.
The Android developer documentation has a roster of such optimizations. Some of the highlights include:
ViewHolderobjects for optimizing an Adapter, consider skipping the accessor methods and just use the fields directly.
Systemare two cited examples. These will run much faster than anything you might create yourself in Java.
The preview of this section was accidentally identified as an Android 'tasty treat' by the Cookie Monster.
The preview of this section was stepped on by Godzilla.