Chapter     3

Performance

Michael A. Jackson famously coined the first and second rules of program optimization:

  • Rule 1. Don’t do it.
  • Rule 2. (For experts only!): Don’t do it yet.

This can be interpreted in a number of ways. To me, it really means “keep your code clean and don’t worry about optimizing its flow.” Avoid making the code too complicated. It also points to the fact that as computers and JIT compilers get more advanced, you probably won’t have to worry about it in six months, as the hardware will have overtaken any minimal optimizations that you can apply.

What it doesn’t mean is to do nothing if your mobile app takes 10 or more seconds to load an activity and is a terrible user experience. Remember that whatever you thought was an acceptable time on the Web is most definitely not acceptable on a phone, whether that’s on Android or iOS.

And it gets worse, because if your app takes too long, then Android will display the dreaded Android Not Responding image (see Figure 3-1), and your user will probably leave the app. This is more likely to happen on older devices with less memory and less power; and contrary to the second law of program optimization there are many of older Android devices from the Gingerbread era that continue to hang around in the field and are not expected to go away any time soon.

9781430258575_Fig03-01.jpg

Figure 3-1. Android “not responding” popup

For comparison’s sake we’re going to talk a little about how performance tuning works on the Web. Optimizing Android apps is still a bit of a black art; while there’s a general consensus on how you should go about optimizing your web server, it’s nowhere as neat and tidy on the Android platform. After all, the variety of Android devices on the market makes optimizing your app a moving target.

We’ll also spend some time on Android performance tips that can really make a difference to your code. Finally, we’ll look at the some of the tools that come with the Android SDK, such as Dalvik Debug Monitor Server (DDMS) and Traceview, that can help identify bottlenecks and hopefully point the way to creating a better app.

History

Back in the 2000s, performance optimization was all about how to optimize web applications typically sitting on IIS or Apache web servers, and many of the same points apply to what we are trying to do in this chapter. Unfortunately, it’s not as easy to measure Android performance as it is on a web server.

The Web server metric that is often aimed for is that 95% of pages should be returned in a second or less. The raw stats, such as the number of page hits and page timings (using the time-taken token, as shown in Figure 3-2), are all there to see in the log files. The trick is to optimize the slowest, most visited pages, which give the perception of a faster web server; perception is reality when it comes to performance. The same is true on mobile devices.

9781430258575_Fig03-02.jpg

Figure 3-2. Web Server log files with time-taken token

Dramatic increases in page speed are commonly achieved on the worst-performing pages by adding indexes on the database, fixing SELECT statements to limit the amount of data returned, or fixing problems with programming control flow logic. Iteratively fixing the most-visited, worst-performing pages over an extended time can transform a web server’s speed using this “wash, rinse, repeat” approach.

Android, on the other hand, is not as simple. In Android there is no metric like “95% of pages should be returned in a second or less.” There really isn’t any consensus on how responsive an app needs to be. And the metric would probably also vary from one device to the next. It’s also a lot harder to measure how long each activity takes, as there are no log files with a handy time-taken token that you can easily use.

It’s not all bad news, however, as the Android SDK does come with a number of tools, such as DDMS and Traceview, that really help debug performance problems, but they measure different aspects of an Android app’s performance.

Ideally, you want a good load testing tool with some sort of reliable time measurement. If possible, it should run as part of a build on a continuous integration server so you could see regression test reports; by seeing how long the same actions are taking as the app progresses, you’ll be able to identify if something is suddenly taking dramatically longer than it was in the past.

We will need to look at Web server statistics when we’re trying to optimize Web services, which we’ll return to later in the book.

Performance Tips

Let’s take a look at some Android, Java, Web services, and SQL tips that you might want to try if your app is not responding correctly.

Android Performance

Google publishes an excellent list of performance tips (see  http://developer.android.com/training/articles/perf-tips.html), which the following are largely taken from and expanded upon. Some of these optimizations take a very macro approach, and some take a very micro approach to optimization and will only remove a line or two of bytecode from the generated classes.dex in the APK. These micro-optimizations will probably be handled by future just-in-time DVM optimizations or ahead of time by the new ART or Android Runtime virtual machine, which is a replacement for the DVM. However as ART is at the time of writing available only on Android KitKat, it may be a while before these automated optimizations become commonplace.

  • Avoid creating unnecessary objects or memory allocations. There are two basic rules for writing efficient code:
  • Don’t do work that you don’t need to do.
  • Don’t allocate memory if you can avoid it.
  • Mobile development is relatively simple right now; we don’t have the layers and layers of complexity that always appear as a technology matures, such EJBs.
  • But it is inevitable that this is going to happen sooner or later on Android. People are already putting ORMs into their Android apps, so try to move to more of a TDD (test-driven development) model, and think about what you’re introducing. Do you really need to reinvent some caching mechanism to satisfy the feature you’re implementing, or not? If you are still worried, then apply the YAGNI concept— You Aren’t Going to Need It, because you really don’t need it.
  • Avoid internal getters/setters. Virtual method calls are expensive, more so than instance field lookups. It’s reasonable to follow common object-oriented programming practices and have getters and setters in the public interface, but within a class you should always access fields directly. This is an example of a micro-optimization that removes a line or two of bytecode from the generated classes.dex in the APK.
  • Use static/final where appropriate. Because of the way the code is compiled into Davlik bytecode, any code that refers to intVal will use the integer value 42 directly if you use a static final, and accesses to strVal will use a relatively inexpensive “string constant” instruction instead of a field lookup.
  • Use floats judiciously. Floating-point calculation is expensive, usually taking twice as long as integer calculations on Android devices.
  • Make fewer, more productive, NDK calls. Context switching between Java and C++ using the JNI or NDK can be expensive. There are also no JIT optimizations.
  • But if the app uses some core algorithm or functionality that doesn’t need to be tied to the UI in any significant way, it should probably be run natively. Running things natively is almost always going to be faster than Java even with the JIT compiler. The NDK also comes with some major security benefits, as it’s much harder to reverse-engineer C++ code.
  • Inflate Views only when needed. Basically, the idea here is that you only inflate the views a minimum number of times or better still delay displaying the view, because inflating views is pretty expensive.
  • Use standard libraries and enhancements. Use libraries rather than rolling your own code. Android also sometimes replaces library methods with optimized hand-coded assembler. For example, using String.indexOf() and the System.arraycopy() method is about nine times faster than a hand-coded loop.
  • Use StrictMode. To limit the chance of an Android Not Responsive (ANR) error, it helps to not include any slow network or disk access in the applications main thread. Android provides a utility called StrictMode, which is typically used to detect if there are any unwanted disk or network accesses introduced into your application during the development process. Add StrictMode to your onCreate() method as shown in Listing 3-1. StrictMode calls are also pretty expensive, so make sure the code isn’t shipped as production code.

    Listing 3-1.   Using the Strictmode utility

    public void onCreate()
    {
            // remove from production code
            if (BuildConfig.DEBUG){
                    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(),
                    .detectDiskReads()
                    .detectDiskWrites()
                    .detectNetwork()
                    .penaltyLog()
                    .build());
         {
            super.onCreate();
      
    }
  • Optimize the onSomething(  ) classes. Earlier we talked about perception being reality for web applications; in the Android world, if your onStart(), onCreate(), and onResume() classes are lightning fast, then the application is going to be perceived to be a faster Android app. So if there is any code that you can put elsewhere or optimizations that you might want to apply, then spending time in these classes will bring rewards. Wait as long as you can to inflate any views. Using android.view.ViewStub will allow objects to be created when needed, a technique known as lazily inflating a view.
  • Use Relativelayouts instead of Linearlayouts. New Android developers tend to create a UI over-using LinearLayouts. As the application becomes more complex, these linear layouts can often become quite nested. Replacing these LinearLayouts with a single RelativeLayout will improve your UI loading speed. Lint and the Hierarchy Viewer will help you identify deeply nested LinearLayouts.

Java Performance

There are books and books written about Java performance, and Android can also benefit from some well-written Java code. The Java Performance Tuning page (http://www.javaperformancetuning.com/tips/rawtips.shtml) is a page of links to articles about Java optimization with summaries and reviews of each of these pages of optimization tips.

The most common optimizations are as follows:

  1. Use + for concatenating two Strings; use Stringbuffer for concatenating more Strings.
  • Don’t synchronize code unless synchronization is required.
  • Close all resources, such as connections and session objects, when finished.
  • Classes and methods that aren’t going to be redefined should be declared as final.
  • Accessing arrays is much faster than accessing vectors, Strings, and StringBuffers.

SQLite Performance

Website inefficiencies could often be summed up as “It’s the database, Stupid.” And while it’s less of an issue on Android, where SQLite is used more for client-side caching of information, there is no reason why EXPLAIN PLAN cannot still be very useful in your performance tuning. And don’t forget you can create indexes on SQLite, too, if you need them (see Figure 3-3).

9781430258575_Fig03-03.jpg

Figure 3-3. SQLite indexes

Learn the SQLite Android libraries and use the DatabaseUtils.InsertHelper command for inserting large amounts of data or use compileStatement where appropriate. Don’t store the database on the SD Card. Finally, don’t return the entire table of data in a SELECT statement; always return the minimum of rows using carefully crafted SQL statements.

Web Services Performance

For Web services it’s a case of “everything old is new again.” We’re right back to the web site optimization techniques I mentioned earlier. Use the server logs, as shown in Figure 3-2 earlier, to see how long each call is taking and optimize the slowest, most-used Web services. Some common optimizations for Web services are as follows:

  • Minimize the size of the Web service envelopes; choose REST over SOAP and JSON over XML where possible.
  • Reduce the number of round trips, steer clear of chatty Web service calls, and keep the number of Web service transactions to a minimum.
  • Remove any duplicate calls, which are not as uncommon as they might seem.
  • Similar to database SELECT * FROM TABLE statements, careful choice of query parameters can dramatically limit the amount of data returned via the Web service.
  • Avoid maintaining state across calls; the most scalable of Web services maintain no state.
  • Gzip the data.

Web proxy tools such as Charles Proxy (http://www.charlesproxy.com/) are an excellent way to see how your app is interacting with Web services.

The topic of Web services is covered in more detail in Chapter 8.

Optimized Code

In the next few pages you’re going to see how some of these optimizations are used in the ToDo List application. To begin, Listing 3-2 shows Splash.java, which has a bare-bones onCreate() method.

Listing 3-2.   The ToDo List Application’s Splash.java page

package com.logicdrop.todos;
  
import android.app.Activity;
import android.os.Bundle;
import android.content.Intent;
  
public class Splash extends Activity {
    public void onCreate(Bundle savedInstanceState) {
        // TIP: Optimized the onSomething() classes, especially onCreate()
        super.onCreate(savedInstanceState);
  
        // TIP: View - inflate the views a minimum number of times
        // inflating views are expensive
        /*for (int i=0; i<10000; i++)
            setContentView(R.layout.splash);*/
  
        // TIP: Splashscreen optional  (DONE)
        setContentView(R.layout.splash);
        Thread timer = new Thread() {
            public void run() {
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    Intent openStartingPoint = new Intent("com.logicdrop.todos.TodoActivity");
                    startActivity(openStartingPoint);
                }
            }
        };
        timer.start();
    }
}
  

ToDoActivity.java, shown in Listing 3-3, has many of the Android and Java optimizations mentioned in this chapter; see comments in the code for more information. It also shows how to stop and start profiling using the Traceview API.

Listing 3-3.   The ToDo List Application’s ToDoActivity.java page

package com.logicdrop.todos;
  
import java.util.List;
import java.util.ArrayList;
  
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.os.StrictMode;
  
import com.logicdrop.todos.R;
  
public class TodoActivity extends Activity
{
     public static final String APP_TAG = "com.logicdrop.todos";
  
     private ListView taskView;
     private Button btNewTask;
     private EditText etNewTask;
     private TodoProvider provider;
  
     // TIP: Use static/final where appropriate
     private final OnClickListener handleNewTaskEvent = new OnClickListener()
     {
          @Override
          public void onClick(final View view)
          {
               Log.d(APP_TAG, "add task click received");
  
               TodoActivity.this.provider.addTask(TodoActivity.this
                         .etNewTask
                                   .getText()
                                   .toString());
  
               TodoActivity.this.renderTodos();
          }
     };
  
    // TIP: Traceview
     @Override
     protected void onStop()
     {
          super.onStop();
  
          // Debug.stopMethodTracing();
     }
  
     @Override
     protected void onStart()
     {
        // Debug.startMethodTracing("ToDo");
  
          super.onStart();
     }
  
     // TIP: Use floats judiciously
     @SuppressWarnings("unused")
     private void showFloatVsIntegerDifference()
     {
          int max = 1000;
          float f = 0;
          int i = 0;
          long startTime, elapsedTime;
  
          // Compute time for floats
          startTime = System.nanoTime();
          for (float x = 0; x < max; x++)
          {
               f += x;
          }
          elapsedTime = System.nanoTime() - startTime;
          Log.v(APP_TAG, "Floating Point Loop: " + elapsedTime);
  
          // Compute time for ints
          startTime = System.nanoTime();
          for (int x = 0; x < max; x++)
          {
               i += x;
          }
          elapsedTime = System.nanoTime() - startTime;
          Log.v(APP_TAG, "Integer Point Loop: " + elapsedTime);
     }
  
     // TIP: Avoid creating unnecessary objects or memory allocation
     private void createPlaceholders()
     {
        // TIP: Avoid internal getters/setters
          provider.deleteAll();
  
          if (provider.findAll().isEmpty())
          {
            // TIP: Arrays are faster than vectors
               List<String> beans = new ArrayList<String>();
  
               // TIP: Use enhanced for loop (DONE)
               // This is example of the enhanced loop but don't allocate objects if not necessary
                  /*for (String task : beans) {
                     String title = "Placeholder ";
                    this.provider.addTask(title);
                    beans.add(title);
                  }*/
  
               /*for (int i = 0; i < 10; i++)
               {
                    String title = "Placeholder " + i;
                    this.getProvider().addTask(title);
                    beans.add(title);
               }*/
          }
     }
  
         // TIP: Avoid private getters/setters - consider using package (DONE)
     /*EditText getEditText()
     {
          return this.etNewTask;
     }*/
  
     /*private TodoProvider getProvider()
     {
          return this.provider;
     }*/
  
     /*private ListView getTaskView()
     {
          return this.taskView;
     }*/
  
     @Override
     public void onCreate(final Bundle bundle)
     {
        // TIP: Use Strictmode to detect unwanted disk or network access
        // Remove from production code (DONE)
        //StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
        //        .detectDiskReads()
        //        .detectDiskWrites()
        //        .detectNetwork()
        //        .penaltyLog()
        //        .build());
          super.onCreate(bundle);
  
             // TIP: Do not overuse Linearlayouts, as they become more complex (DONE)
             // Replace them with Relativelayouts, increasing UI loading speed
          this.setContentView(R.layout.main);
  
          this.provider = new TodoProvider(this);
          this.taskView = (ListView) this.findViewById(R.id.tasklist);
          this.btNewTask = (Button) this.findViewById(R.id.btNewTask);
          this.etNewTask = (EditText) this.findViewById(R.id.etNewTask);
          this.btNewTask.setOnClickListener(this.handleNewTaskEvent);
  
          this.renderTodos();
  
          // TIP: Again, don't allocate unnecessary objects that expand the heap size to significant proportions (DONE)
          // Once GC occurs, a large amount of the heap memory is dumped, especially with
          // local data structures, which renders a large portion of the heap unused.
          // SEE: optimizedHeap.png, deoptimizedHeap.png, heap-before.tiff, heap-after.tiff
          /*ArrayList<uselessClass> uselessObject = new ArrayList<uselessClass>();
          for (int i=0; i<180000; i++)
              uselessObject.add(new uselessClass());*/
     }
  
     private void renderTodos()
     {
          final List<String> beans = this.provider.findAll();
  
          Log.d(TodoActivity.APP_TAG, String.format("%d beans found", beans.size()));
  
          this.taskView.setAdapter(new ArrayAdapter<String>(this,
                    android.R.layout.simple_list_item_1,
                    beans.toArray(new String[]
                    {})));
  
          this.taskView.setOnItemClickListener(new OnItemClickListener()
          {
               @Override
               public void onItemClick(final AdapterView<?> parent, final View view, final int position, final long id)
               {
                    Log.d(TodoActivity.APP_TAG, String.format("item with id: %d and position: %d", id, position));
  
                    final TextView v = (TextView) view;
                    TodoActivity.this.provider.deleteTask(v.getText().toString());
                    TodoActivity.this.renderTodos();
               }
          });
     }
  
    // Class with 26 double data members used to expand heap size in example
    /*private class uselessClass {
        double a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;
    }*/
}
  

Finally, ToDoProvider.java, shown in Listing 3-4, has examples of some of the remaining optimizations, such as always closing resources and only using SELECT statements to return a minimum of data.

Listing 3-4.   The ToDo List Application’s ToDoProvider.java Page

package com.logicdrop.todos;
  
import java.util.ArrayList;
import java.util.List;
  
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
  
final class TodoProvider
{
     private static final String DB_NAME = "tasks";
     private static final String TABLE_NAME = "tasks";
     private static final int DB_VERSION = 1;
     private static final String DB_CREATE_QUERY = "CREATE TABLE " + TodoProvider.TABLE_NAME + " (id integer primary key autoincrement, title text not null);";
  
     // TIP: Use final wherever possible (DONE)
     private final SQLiteDatabase storage;
     private final SQLiteOpenHelper helper;
  
     public TodoProvider(final Context ctx)
     {
          this.helper = new SQLiteOpenHelper(ctx, TodoProvider.DB_NAME, null, TodoProvider.DB_VERSION)
          {
               @Override
               public void onCreate(final SQLiteDatabase db)
               {
                    db.execSQL(TodoProvider.DB_CREATE_QUERY);
               }
  
               @Override
               public void onUpgrade(final SQLiteDatabase db, final int oldVersion,
                         final int newVersion)
               {
                    db.execSQL("DROP TABLE IF EXISTS " + TodoProvider.TABLE_NAME);
                    this.onCreate(db);
               }
          };
  
          this.storage = this.helper.getWritableDatabase();
     }
  
     // TIP: Avoid synchronization (DONE)
     public void addTask(final String title)
     {
          final ContentValues data = new ContentValues();
          data.put("title", title);
  
          this.storage.insert(TodoProvider.TABLE_NAME, null, data);
     }
  
     public void deleteAll()
     {
          this.storage.delete(TodoProvider.TABLE_NAME, null, null);
     }
  
     public void deleteTask(final long id)
     {
          this.storage.delete(TodoProvider.TABLE_NAME, "id=" + id, null);
     }
  
     public void deleteTask(final String title)
     {
          this.storage.delete(TodoProvider.TABLE_NAME, "title='" + title + "'", null);
     }
  
     // TIP: Don't return the entire table of data. (DONE)
     // Unused
     public List<String> findAll()
     {
          Log.d(TodoActivity.APP_TAG, "findAll triggered");
  
          final List<String> tasks = new ArrayList<String>();
  
          final Cursor c = this.storage.query(TodoProvider.TABLE_NAME, new String[]
          { "title" }, null, null, null, null, null);
  
          if (c != null)
          {
               c.moveToFirst();
  
               while (c.isAfterLast() == false)
               {
                    tasks.add(c.getString(0));
                    c.moveToNext();
               }
  
               // TIP: Close resources (DONE)
               c.close();
          }
  
          return tasks;
     }
}

Tools

In this section we’ll look at two types of tools useful in finding performance bottlenecks—tools that come with the Android SDK, and Unix command-line tools.

The Android SDK ships with the following tools to help us identify any performance issues:

  • DDMS
  • Traceview
  • Lint
  • Hierarchy Viewer
  • Viewer

The Dalvik Debug Monitor Server (DDMS) is an Android SDK application that works as either a standalone tool or an Eclipse plugin. DDMS does lots of things, including device screen capture and providing a place to find logging output. But it also provides heap analysis, method allocation, and thread monitoring information. The Android SDK also has the Traceview tool for method profiling, layoutopt for optimizing your XML layouts, and Hierarchy Viewer for optimizing your UI.

And because Android is basically a Linux shell, we can leverage many of the following command-line Unix tools for performance testing:

  • Top
  • Dumpsys
  • Vmstat
  • Procstats

In this section we’re going to look at how to use those tools to get a quick idea of where your application is spending most of its time.

DDMS

In this section we’ll be covering the System Performance, Heap Usage, Threads, and Traceview tools, all of which come as part of DDMS. We’ll also look at the Memory Analyzer Tool (MAT), which can be downloaded as part of the Eclipse tool and used to report on how memory is being managed in the Heap.

System Performance

The most basic tool in the DDMS suite is System Performance, which gives a quick snapshot overview of the current CPU load, memory usage, and frame render time, as shown in Figure 3-4. The first sign that you have an underperforming app is when your application is consuming too much CPU or memory.

9781430258575_Fig03-04.jpg

Figure 3-4. The System Performance tool displaying CPU load for CallCenterApp

Heap Usage

DDMS also offers a Heap Usage tool. Take the following steps to view the memory heap, where you can see what objects are being created and if they’re being destroyed correctly by the garbage collection. (See Figure 3-5.)

  1. In the Devices tab, select the process for which you want to view the heap.
  2. Click the Update Heap button to enable heap information for the process.
  3. Click Cause GC in the Heap tab to invoke garbage collection, which enables the collection of heap data.
  4. When garbage collection completes, you will see a group of object types and the memory that has been allocated for each type.
  5. Click an object type in the list to see a bar graph that shows the number of objects allocated for a particular memory size in bytes.
  6. Click Cause GC again to refresh the data. Details of the heap are given along with a graph of allocation sizes for a particular allocation type. Watch the overall trend in Heap Size to make sure it doesn’t keep growing during the application run.

9781430258575_Fig03-05.jpg

Figure 3-5. Viewing the DDMS heap

Eclipse Memory Analyzer

Eclipse has an integrated Memory Analyzer Tool (MAT) plugin, which you can download and install from http://www.eclipse.org/mat/downloads.php. MAT can help you make sense of the heap output. Now when you dump the heap profile or hprof file (see Figure 3-6), it will be automatically analyzed so you can make some sense of the heap file.

9781430258575_Fig03-06.jpg

Figure 3-6. Dumping the hprof file

MAT provides a number of reports, including a Dominator Tree for the biggest class, a Top Consumers report, and a Leak Suspects report. Figure 3-7 shows Biggest Objects by Retained Size.

9781430258575_Fig03-07.jpg

Figure 3-7. Memory Analyzer Tool overview

Memory Allocation

The next level of detail about allocations is shown in the Allocation Tracker view (Figure 3-8). To display it, click Start Tracking, perform an action in the application, and then click Get Allocations. The list presented is in allocation order, with the most recent memory allocation displayed first. Highlighting it will give you a stack trace showing how that allocation was created.

9781430258575_Fig03-08.jpg

Figure 3-8. Allocation Tracker

Threads

The thread monitor and profiling view in DDMS is useful for applications that manage a lot of threads. To enable it, click the Update Threads icon, shown in Figure 3-9.

9781430258575_Fig03-09.jpg

Figure 3-9. DDMS threads

The total time spent in a thread running user code (utime) and system code (stime) is measured in what are known as jiffies. A jiffy was originally the time it takes light to travel 1cm, but for Android devices it is the duration of one tick of the system timer interrupt. It varies from device to device but is generally accepted to be about 10ms. The asterisk indicates a daemon thread, and the status Native means the thread is executing native code.

Looking at the sample data in Figure 3-9, it is clear that an unusual amount of time is spent doing GC. A closer look at how the application is handling object creation might be a good idea for improving performance.

Method Profiling

Method Profiling is the tool of choice within DDMS for getting a quick overview of where time is really spent in your application and is the first step in homing in on methods that are taking too much time. With your application running and ideally performing some interesting task that you would like to get more performance data about, take the following steps to use Method Profiling:

  1. Click on Start Method Profiling.
  2. Click the icon again to stop collection after a couple of seconds.
  3. The IDE will automatically launch the Traceview window and allow you to analyze the results from right within the IDE.
  4. Click a method call in the bottom pane to create a hierarchy, showing you the current method, the parent(s) that call this method, and then the children methods called from within the selected method (Figure 3-10).

    9781430258575_Fig03-10.jpg

    Figure 3-10. Method Profiling in DDMS using Traceview

  5. Identify the methods that are taking the most time so you can look at them closer by creating Traceview files, which we’ll explore later in this section.

Each method has its parents and children, and the columns are as follows:

  • Inc % The percentage of the total time spent in the method plus any called methods
  • Inclusive The amount of time spent in the method plus the time spent in any called methods
  • Excl % The percentage of the total time spent in the method
  • Exclusive  The amount of time spent in the method
  • Calls + Recursive  The number of calls to this method plus any recursive calls
  • Time/Call The average amount of time per call

Traceview

Once you’ve identified the methods to take a closer look at, you can use the command-line version of Traceview with the tracing API for more accurate measurement. Add Debug.startMethodTracing, and Debug.stopMethodTracing around the code you want to profile, as shown in Listing 3-5. Compile your code again and push the APK to your device.

Listing 3-5.   startMethodTracing and stopMethodTracing

public class ScoresActivity extends ListActivity {
        public void onStart() {
                // start tracing to "/sdcard/scores.trace"
                Debug.startMethodTracing("scores");
                super.onStart();
                // other start up code here
        }
  
        public void onStop() {
                super.onStop();
                // other shutdown code here
                Debug.stopMethodTracing();
        }
  
        // Other implementation code
}
  

The trace file can now be pulled off the device and displayed in Traceview using the following commands:

  
adb pull /sdcard/scores.trace scores.before.trace
  

Figure 3-11 shows the results before code optimization.

9781430258575_Fig03-11.jpg

Figure 3-11. The trace file before optimization

Optimize the code using some of the suggestions earlier in the chapter and measure again, this time using the following command:

  
adb pull /sdcard/scores.trace scores.after.trace
  

Figure 3-12 shows the results after optimization; the difference is clear.

9781430258575_Fig03-12.jpg

Figure 3-12. The trace file after optimization

Lint

Lint is, like its original Unix namesake, a static code-analysis tool. It replaces the layoutopt tool, which was used to analyze your layout files and point out potential performance issues to get quick performance gains by reorganizing your UI layout. It now does so much more, including the following error-checking categories:

  • Correctness
  • Correctness:Messages
  • Security
  • Performance
  • Usability:Typography
  • Usability:Icons
  • Usability
  • Accessibility
  • Internationalization

If you run the command lint --list Performanceit will tell you that Lint does the following performance checks, many of which we’ve already seen in the Android Tips section:

  • FloatMath: Suggests replacing android.util.FloatMath calls with java.lang.Math.
  • FieldGetter: Suggests replacing use of getters with direct field access within a class.
  • InefficientWeight: Looks for inefficient weight declarations in LinearLayouts.
  • NestedWeights: Looks for nested layout weights, which are costly.
  • DisableBaselineAlignment: Looks for LinearLayouts, which should set android:baselineAligned=false.
  • ObsoleteLayoutParam: Looks for layout params that are not valid for the given parent layout.
  • MergeRootFrame: Checks whether a root <FrameLayout> can be replaced with a <merge> tag.
  • UseCompoundDrawables: Checks whether the current node can be replaced by a TextView using compound drawables.
  • UselessParent: Checks whether a parent layout can be removed.
  • UselessLeaf: Checks whether a leaf layout can be removed.
  • TooManyViews: Checks whether a layout has too many views.
  • TooDeepLayout: Checks whether a layout hierarchy is too deep.
  • ViewTag: Finds potential leaks when using View.setTag.
  • HandlerLeak: Ensures that Handler classes do not hold on to a reference to an outer class.
  • UnusedResources: Looks for unused resources.
  • UnusedIds: Looks for unused IDs.
  • SecureRandom: Looks for suspicious usage of the SecureRandom class.
  • Overdraw: Looks for overdraw issues (where a view is painted only to be fully painted over).
  • UnusedNamespace: Finds unused namespaces in XML documents.
  • DrawAllocation: Looks for memory allocations within drawing code.
  • UseValueOf: Looks for instances of “new” for wrapper classes, which should use valueOf instead.
  • UseSparseArrays: Looks for opportunities to replace HashMaps with the more efficient SparseArray.
  • Wakelock: Looks for problems with wakelock usage.
  • Recycle: Looks for missing recycle() calls on resources.

Lint can be run from within Eclipse or on the command line. If you just want to run the performance checks on your project, type lint --check Performance < ProjectName > on the command line. Listing 3-6 displays the output of this command for the sample application, showing that there are some layouts that need to be better organized.

Listing 3-6.   Lint Performance output for the CallCenterApp project

Scanning CallCenterV3: ...................................................
Scanning CallCenterV3 (Phase 2): ......................
reslayoutcustom_titlebar.xml:6: Warning: Possible overdraw: Root element paints background #004A82 with a theme that also paints a background (inferred theme is @style/CustomTheme) [Overdraw]
    android:background="#004A82"
    ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
reslayoutcustom_titlebar_with_logout.xml:6: Warning: Possible overdraw: Root element paints background #004A82 with a theme that also paints a background (inferred theme is @style/CustomTheme) [Overdraw]
    android:background="#004A82"
    ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
reslayoutcustom_titlebar_with_settings.xml:6: Warning: Possible overdraw: Root element paints background #004A82 with a theme that also paints a background (inferred theme is @style/CustomTheme) [Overdraw]
    android:background="#004A82"
    ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
reslayoutlogin_screen.xml:5: Warning: Possible overdraw: Root element paints background @drawable/bg_app with a theme that also paints a background (inferred theme is @style/CustomTheme) [Overdraw]
    android:background="@drawable/bg_app"
    ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
reslayoutqueues_screen.xml:5: Warning: Possible overdraw: Root element paints background @drawable/bg_app with a theme that also paints a background (inferred theme is @style/CustomTheme) [Overdraw]
    android:background="@drawable/bg_app"
    ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
reslayoutsettings_screen.xml:5: Warning: Possible overdraw: Root element paints background #1D1D1D with a theme that also paints a background (inferred theme is @style/CustomTheme) [Overdraw]
    android:background="#1D1D1D"
    ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
resdrawable-hdpig_login.9.png: Warning: The resource R.drawable.bg_login appears to be unused [UnusedResources]
resdrawable-hdpitn_ok_xlarge.png: Warning: The resource R.drawable.btn_ok_xlarge appears to be unused [UnusedResources]
resdrawable-hdpi o_xlarge.png: Warning: The resource R.drawable.no_xlarge appears to be unused [UnusedResources]
resmenusettings_menu.xml: Warning: The resource R.menu.settings_menu appears to be unused [UnusedResources]
resvaluesstrings.xml:7: Warning: The resource R.string.loginMessage appears to be unused [UnusedResources]
    <string name="loginMessage">Enter Your Login Credentials</string>
            ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
resvaluesstrings.xml:8: Warning: The resource R.string.CSQ_default appears to be unused [UnusedResources]
    <string name="CSQ_default">Log In</string>
            ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
resvaluesstrings.xml:11: Warning: The resource R.string.default_time appears to be unused [UnusedResources]
    <string name="default_time">00:00:00</string>
            ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
resvaluesstrings.xml:12: Warning: The resource R.string.oldest_in_queue appears to be unused [UnusedResources]
    <string name="oldest_in_queue">Oldest Call In Queue: </string>
            ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
resvaluesstrings.xml:16: Warning: The resource R.string.add_to_queue appears to be unused [UnusedResources]
    <string name="add_to_queue">Add To Queue</string>
            ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
reslayoutlogin_screen.xml:9: Warning: This LinearLayout view is useless (no children, no background, no id, no style) [UselessLeaf]
    <LinearLayout
    ^
reslayoutcustom_titlebar.xml:10: Warning: This RelativeLayout layout or its LinearLayout parent is useless; transfer the background attribute to the other view [UselessParent]
    <RelativeLayout
    ^
reslayoutcustom_titlebar_with_logout.xml:10: Warning: This RelativeLayout layout or its LinearLayout parent is useless; transfer the background attribute to the other view [UselessParent]
    <RelativeLayout
    ^
reslayoutcustom_titlebar_with_settings.xml:10: Warning: This RelativeLayout layout or its LinearLayout parent is useless; transfer the background attribute to the other view [UselessParent]
    <RelativeLayout
    ^
reslayoutqueue_list_item.xml:13: Warning: This TableRow layout or its TableLayout parent is possibly useless [UselessParent]
        <TableRow
        ^
reslayoutqueue_list_item.xml:45: Warning: This TableRow layout or its TableLayout parent is possibly useless [UselessParent]
        <TableRow
        ^
reslayoutcustom_titlebar.xml:3: Warning: The resource R.id.photo_titlebar appears to be unused [UnusedIds]
    android:id="@+id/photo_titlebar"
    ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
reslayoutqueue_list_item.xml:7: Warning: The resource R.id.nameTable appears to be unused [UnusedIds]
        android:id="@+id/nameTable"
        ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
reslayoutqueue_list_item.xml:14: Warning: The resource R.id.tableRow1 appears to be unused [UnusedIds]
            android:id="@+id/tableRow1"
            ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
reslayoutqueue_list_item.xml:19: Warning: The resource R.id.activeIndicatorDummy appears to be unused [UnusedIds]
                android:id="@+id/activeIndicatorDummy"
                ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
reslayoutqueue_list_item.xml:46: Warning: The resource R.id.tableRow2 appears to be unused [UnusedIds]
            android:id="@+id/tableRow2"
            ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
reslayoutqueue_list_item.xml:62: Warning: The resource R.id.callsInQueueLabel appears to be unused [UnusedIds]
                android:id="@+id/callsInQueueLabel"
                ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
0 errors, 27 warnings
  
reslayoutqueue_list_item.xml:7: Warning: The resource R.id.nameTable appears to be unused [UnusedIds]
        android:id="@+id/nameTable"
        ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
reslayoutqueue_list_item.xml:14: Warning: The resource R.id.tableRow1 appears to be unused [UnusedIds]
            android:id="@+id/tableRow1"
            ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
reslayoutqueue_list_item.xml:19: Warning: The resource R.id.activeIndicatorDummy appears to be unused [UnusedIds]
                android:id="@+id/activeIndicatorDummy"
                ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
reslayoutqueue_list_item.xml:46: Warning: The resource R.id.tableRow2 appears to be unused [UnusedIds]
            android:id="@+id/tableRow2"
            ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
reslayoutqueue_list_item.xml:62: Warning: The resource R.id.callsInQueueLabel appears to be unused [UnusedIds]
                android:id="@+id/callsInQueueLabel"
                ∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼∼
0 errors, 27 warnings

Hierarchy Viewer

Another useful tool in debugging performance issues, specifically for layouts, is the Hierarchy Viewer. At its most basic it will show you how long it takes to inflate the layouts. You start Hierarchy Viewer from within Eclipse by adding the perspective; this is similar to the way you would add back DDMS if it ever disappeared.

Hierarchy Viewer first displays a list of devices and emulators; click the name of your app from the list and then click Load View Hierarchy. The Tree View, the Tree Overview, and the Tree Layout will then open, as shown in Figure 3-13. The Tree View shows all the layouts that you defined in your XML files. We talked earlier in this chapter about how nested layouts can be bad for performance, and Tree Overview is a great way to see just how nested your layouts have become and figure out if it’s time to merge them into a RelativeLayout. Tree View shows how long each layout took to display, so you can identify which views you need to debug and optimize to speed up your UI.

9781430258575_Fig03-13.jpg

Figure 3-13. Hierarchy Viewer for CallCenterApp login screen

In Figure 3-13 we can see that our login view took almost 33ms to display. It also shows what layouts are part of the login view, and by hovering over specific views you can see just how long each took to display.

Hierarchy Viewer also includes a Pixel Perfect tool for designers. We won’t be covering that in this book.

Unix Tools

Because Android is built on Linux, we can leverage many of the same shell command tools as Linux for performance testing. The main tools focus on total process load, individual process details, and memory utilization.

Top

The top command will give you an idea of where your app is in relation to all other processes on the device. The higher up the list, the more resources it is consuming. You can log onto the phone using the adb shell command, or you can run the command remotely using adb shell top from your command line. Figure 3-14 shows the results.

9781430258575_Fig03-14.jpg

Figure 3-14. Output from the top command

Dumpsys

Top also gets you the process ID or PID of your application, which you can then use for the dumpsys command, as follows:

  
adb shell dumpsys meminfo 1599
  

Dumpsys will give you information about the memory and heap being used by your application; see Figure 3-15.

9781430258575_Fig03-15.jpg

Figure 3-15. Dumpsys Meminfo

All of the Unix tools mentioned in this section are taking measurements at a point in time. Procstats was introduced in Android 4.4 or KitKat to show how much memory and CPU the apps running in the background will consume. Use the command to see the procstats output:

  
adb shell dumpsys procstats
  

with the results shown in Figure 3-16.

9781430258575_Fig03-16.jpg

Figure 3-16. Dumpsys Procstats

Vmstat

Vmstat allows you to view virtual memory levels on the device; see Figure 3-17. It is a simple Linux command that reports about processes, memory, paging, block IO, traps, and CPU activity. The “b” column shows which processes are blocked. Use the command as follows: adb shell vmstat.

9781430258575_Fig03-17.jpg

Figure 3-17. Dumpsys Meminfo

Summary

In this chapter we’ve looked at the tools to first find out if you have a performance problem and then identify the call that needs to be fixed; we also saw some techniques you can use to optimize your application. The Android SDK, and the Android platform, because of their close Unix relationship, come with a wealth of tools that can help you identify issues.

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

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