Performance
Michael A. Jackson famously coined the first and second rules of program optimization:
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.
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.
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.
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();
}
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:
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).
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.
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:
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.
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;
}
}
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:
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:
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.
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.
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.
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.)
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.
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.
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.
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.
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:
Figure 3-10. Method Profiling in DDMS using Traceview
Each method has its parents and children, and the columns are as follows:
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.
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.
Figure 3-12. The trace file after optimization
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:
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:
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
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.
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.
Figure 3-14. Output from the top command
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.
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.
Figure 3-16. Dumpsys Procstats
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.
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.
13.58.53.238