Performance is not only about raw speed. Your application will be perceived as being fast as long as it appears fast to the user, and to appear fast your application must be responsive. As an example, to appear faster, your application can defer allocations until objects are needed, a technique known as lazy initialization, and during the development process you most likely want to detect when slow code is executed in performance-sensitive calls.
The following classes are the cornerstones of most Android Java applications:
Application
Activity
Service
ContentProvider
BroadcastReceiver
Fragment
(Android 3.0 and above)View
Of particular interest in these classes are all the onSomething()
methods that are called from the main thread, such as onStart()
and onFocusChanged()
. The main thread, also referred to as the UI thread, is basically the thread your application runs in. It is possible, though not recommended, to run all your code in the main thread. The main thread is where, among other things:
View.onKeyDown()
and Activity.onKeyLongPress()
).View.onDraw()
).Activity.onCreate()
).NOTE: Many methods are called from the main thread by design. When you override a method, verify how it will be called. The Android documentation does not always specify whether a method is called from the main thread.
In general, the main thread keeps receiving notifications of what is happening, whether the events are generated from the system itself or from the user. Your application has only one main thread, and all events are therefore processed sequentially. That being said, it becomes easy now to see why responsiveness could be negatively affected: the first event in the queue has to be processed before the subsequent events can be handled, one at a time. If the processing of an event takes too long to complete, then other events have to wait longer for their turn.
An easy example would be to call computeRecursivelyWithCache
from the main thread. While it is reasonably fast for low values of n, it is becoming increasingly slower as n grows. For very large values of n you would most certainly be confronted with Android's infamous Application Not Responding (ANR) dialog. This dialog appears when Android detects your application is unresponsive, that is when Android detects an input event has not been processed within 5 seconds or a BroadcastReceiver hasn't finished executing within 10 seconds. When this happens, the user is given the option to simply wait or to “force close” the application (which could be the first step leading to your application being uninstalled).
It is important for you to optimize the startup sequence of all the activities, which consists of the following calls:
onCreate
onStart
onResume
Of course, this sequence occurs when an activity is created, which may actually be more often than you think. When a configuration change occurs, your current activity is destroyed and a new instance is created, resulting in the following sequence of calls:
onPause
onStop
onDestroy
onCreate
onStart
onResume
The faster this sequence completes, the faster the user will be able to use your application again. One of the most common configuration changes is the orientation change, which signifies the device has been rotated.
NOTE: Your application can specify which configuration changes each of its activities wants to handle itself with the activity element's android:configChanges attribute in its manifest. This would result in onConfigurationChanged() being called instead of having the activity destroyed.
Your activities' onCreate()
methods will most likely contain a call to setContentView
or any other method responsible for inflating resources. Because inflating resources is a relatively expensive operation, you can make the inflation faster by reducing the complexity of your layouts (the XML files that define what your application looks like). Steps to simplify the complexity of your layouts include:
NOTE: Pay special attention to your layouts in ListView as there could be many items in the list. Use the SDK's layoutopt tool to analyze your layouts.
The basic rule is to keep anything that is done in the main thread as fast as possible in order to keep the application responsive. However, this often translates to doing as little as possible in the main thread. In most cases, you can achieve responsiveness simply by moving operations to another thread or deferring operations, two techniques that typically do not result in code that is much harder to maintain. Before moving a task to another thread, make sure you understand why the task is too slow. If this is due to a bad algorithm or bad implementation, you should fix it since moving the task to another thread would merely be like sweeping dust under a carpet.
Procrastination does have its merits after all. A common practice is to perform all initializations in a component's onCreate()
method. While this would work, it means onCreate()
takes longer to return. This is particularly important in your application's activities: since onStart()
won't be called until after onCreate()
returns (and similarly onResume()
won't be called until after onStart()
returns), any delay will cause the application to take longer to start, and the user may end up frustrated.
For example, Android uses the lazy initialization concept with android.view.ViewStub, which is used to lazily inflate resources at runtime. When the view stub is made visible, it is replaced by the matching inflated resources and becomes eligible for garbage collection.
Since memory allocations take time, waiting until an object is really needed to allocate it can be a good option. The benefits of lazily allocating an object are clear when an object is unlikely to be needed at all. An example of lazy initialization is shown in Listing 1–14, which is based on Listing 1–13. To avoid always having to check whether the object is null, consider the factory method pattern.
int n = 100;
if (cache == null) {
// createCache allocates the cache object, and may be called from many places
cache = createCache();
}
BigInteger fN = cache.get(n);
if (fN == null) {
fN = Fibonacci. computeRecursivelyWithCache(n);
cache.put(n, fN);
}
Refer to Chapter 8 to learn how to use and android.view.ViewStub in an XML layout and how to lazily inflate a resource.
You should always assume the following two things when writing your application:
As a consequence, you should always try not to perform any network or file system access in your application's main thread as slow operations may affect responsiveness. While your development environment may never experience any network issue or any file system performance problem, your users may not be as lucky as you are.
NOTE: SD c
ards do not all have the same “speed”. If your application depends heavily on the performance of external storage then you should make sure you test your application with various SD cards from different manufacturers.
Android provides a utility to help you detect such defects in your application. StrictMode is a tool that does its best to detect bad behavior. Typically, you would enable StrictMode when your application is starting, i.e when its onCreate()
method is called, as shown in Listing 1–15.
public class MyApplication extends Application {
@Override
public void onCreate ()
{
super.onCreate();
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectCustomSlowCalls() // API level 11, to use with StrictMode.noteSlowCode
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.penaltyFlashScreen() // API level 11
.build());
// not really performance-related, but if you use StrictMode you might as well
define a VM policy too
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects() // API level 11
.setClassInstanceLimit(Class.forName(“com.apress.proandroid.SomeClass”), 100) //
API level 11
.penaltyLog()
.build());
}
}
StrictMode was introduced in Android 2.3, with more features added in Android 3.0, so you should make sure you target the correct Android version and make sure your code is executed only on the appropriate platforms, as shown in Listing 1–12.
Noteworthy methods introduced in Android 3.0 include detectCustomSlowCall()
and noteSlowCall()
, both being used to detect slow, or potentially slow, code in your application. Listing 1–16 shows how to mark your code as potentially slow code.
public class Fibonacci {
public static BigInteger computeRecursivelyWithCache(int n)
{
StrictMode.noteSlowCall(“computeRecursivelyWithCache”); // message can be
anything
SparseArray<BigInteger> cache = new SparseArray<BigInteger>();
return computeRecursivelyWithCache(n, cache);
}
…
}
A call to computeRecursivelyWithCache
from the main thread that takes too long to execute would result in the following log if the StrictMode Thread policy is configured to detect slow calls:
StrictMode policy violation; ~duration=21121 ms:
android.os.StrictMode$StrictModeCustomViolation: policy=31 violation=8 msg=
computeRecursivelyWithCache
Android provides some helper methods to make it easier to allow disk reads and writes from the main thread temporarily, as shown in Listing 1–17.
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
// read something from disk
StrictMode.setThreadPolicy(oldPolicy);
There is no method for temporarily allowing network access, but there is really no reason to allow such access even temporarily in the main thread as there is no reasonable way to know whether the access will be fast. One could argue there is also no reasonable way to know the disk access will be fast, but that's another debate.
NOTE: Enable StrictMode only during development, and remember to disable it when you deploy your application. This is always true, but even more true if you build the policies using the detectAll()
methods as future versions of Android may detect more bad behaviors.
18.191.95.74