Responsiveness

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:

  • Key events are received (for example, View.onKeyDown() and Activity.onKeyLongPress()).
  • Views are drawn (View.onDraw()).
  • Lifecycle events occur (for example, 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:

  • Use RelativeLayout instead of nested LinearLayouts to keep layouts as “flat” as possible. In addition to reducing the number of objects allocated, it will also make processing of events faster.
  • Use ViewStub to defer creation of objects (see the section on lazy initialization).

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.

Lazy initializations

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.

Listing 1–14. Lazily Allocating the Cache

    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.

StrictMode

You should always assume the following two things when writing your application:

  • The network is slow (and the server you are trying to connect to may not even be responding).
  • File system access is slow.

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.

Listing 1–15. Enabling StrictMode in Your Application

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.

Listing 1–16. Marking Your Own Code as Potentially Slow

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.

Listing 1–17. Modifying the Thread Policy to Temporarily Allow Disk Reads

    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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.155.130