Unless you’re really good or really lucky, the applications you write for Android will not be perfect the first time you run them. Fortunately, Eclipse and the Android Software Development Kit provide a rich set of tools for debugging, and even some features that make it easier to write correct code. We’ll take a look at the set of available tools in this chapter, and provide some pointers to other places you can look for even more information on some of the tools.
Throughout the development lifecycle of writing, building, and running the application, the primary tools Android developers use are:
A specific text editor for Java that Android SDK has informed about the Android programming environment. The editor not only warns you about code the compiler can’t parse, but also gives you a wealth of information about what it can.
Recall that Android converts your Java application to run on the Dalvik virtual machine under Android. The Java compiler and the Dalvik translator both provide error information if they can’t build your application.
Eclipse provides a source-level debugger that the Android SDK connects with the running Dalvik bytecode, so you have all the debug capability you’d normally expect from a Java program running under Eclipse.
Android also provides a general-purpose logging package that you can take advantage of to log informational or error messages from your running application. Perhaps of more importance, Android uses this facility extensively to tell you what is going on as it starts up, initiates your application, and tries to run it. There is also a special logcat log for telephony-related messages.
adb
)This provides a command-line debugging interface to a running Android phone or emulator.
Android also provides a special window-oriented debugging environment custom tailored for Android and the Dalvik VM.
An Android-specific utility that tracks all the method calls your application executed and the time spent in each method.
The Android SDK takes full advantage of the features built into the Eclipse IDE, including those in the Eclipse text editor, which is customized for Java source code development. Let’s use a simple application as an example of some of that editor’s features. If you’re already an expert on using Eclipse for Java development, you can skip this section. If you’re new to Eclipse (or new to Java), there are some hints here that will speed up your development of Android applications.
We’ve created a new Android project called DebugTest, using Eclipse and the Android SDK (File → New → Project → Android Project). When you do that, and open the Java source file the SDK created for you, you get a central pane that looks like Figure 5-1. This is the Eclipse Java text editor, and it is already doing its job to point out errors in the nascent application.
In this case, the error indication is in the left margin: the
little lightbulb and red X on line 11. Within that line, the editor
has underlined the R
in R.layout.main
to tell you specifically
where there’s a problem. Editors in Eclipse are smart enough to
understand the syntax of the language they are editing, and in this
case, the error flag is telling us there’s a problem with this part
of the code. If we use the mouse to hover over the R
, we get a pop up that gives us more
information, as shown in Figure 5-2. If you hover your
mouse over the symbols in the left margin, you get the same pop
up.
Notice also that there’s a little red indicator in the upper-right area of the pane, indicating there is an error somewhere in this file, and a little red open rectangle in the right margin. If this file were big enough to need the vertical scroll bar, you could easily see the locations of the errors in the file, and you could scroll to them by dragging the scroll segment to the red rectangle. Eclipse makes it very easy to see where it has found errors in your code.
A quick check of the Package Explorer pane shows that there’s no R.java file. Of course not! It doesn’t
exist, because we haven’t built the project yet, and that’s why
resources under R
can’t be
resolved. After we build DebugTest (Project → Build All), the error
goes away (both the red underline and the symbols in the
margin).
So let’s add some code for a simple application and see some of Eclipse’s debug features. We’ll edit DebugTest.java and main.xml to add a label, a text box, a WebView (we want some Internet action to give us more to look at), and a button. The application will be a trivial browser, with the box being the URL and the button being the trigger to go load the URL into the WebView. We’ll throw in some intentional errors to see how Eclipse handles them.
Our altered main.xml file now looks like this:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Enter URL:" /> <EditText android:id="@+id/URL" android:layout_width="fill_parent" android:layout_height="60.0dip" android:maxLines="1" <Button android:id="@+id/btnGo" android:layout_width="wrap_content" android:layout_height="60.0dip" android:text="Go" /> <WebView android:id="@+id/wvBrowser" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <LinearLayout>
and DebugTest.java looks like this:
package com.oreilly.debug; import android.app.Activity; import android.os.Bundle; public class DebugTest extends Activity { private EditText txtURL; private Button btnGo; private WebView wvBrowser; // Set up an onClick routine to gather URLs entered by the user private final Button.OnClickListener btnGoOnClick = new Button.OnClickListener() { public void onClick(View v) { try { wvBrowser.loadURL(); } catch (Exception e) {} } }; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Find the Views in the layout file txtURL = (EditText) findViewById(R.id.txtURL); btnGo = (Button) findViewById(R.id.btnGo); btnGo.setOnClickListener(btnGoOnClick); wvBrowser = (WebView) findViewById(R.id.wvBrowser); } }
If you type in these lines (instead of copying and pasting),
you’ll see that the editor tries to anticipate what you might type
given the context of where you are in the code. As you type
“wvBrowser.”, for example (including the final dot), the editor
knows that wvBrowser
is a WebView, so it gives you a
list of methods and variables that WebViews have. This is a great
feature that really helps cut down on mistyped method and variable
names. Once you’ve typed or selected the method, the editor shows
you the parameters for that method, so you don’t have to look those
up either.
Since we need to access the Internet to get web pages, we ask for that permission in AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.oreilly.debug" android:versionCode="1" android:versionName="1.0.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".DebugTest" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.INTERNET"></uses-permission> </manifest>
Looking at main.xml in the Eclipse editor pane (now an XML editor, but with many of the same features we saw in the Java editor), we see some errors (Figure 5-3).
A quick inspection confirms what the editor is telling us—that
there’s no close tag for the EditText
. We type />
into
line 17, and the red underlines immediately go away. Now that the
EditText
tag is fixed, we’re left
with one more error, shown in Figure 5-4.
It says we’re missing the end tag for LinearLayout, but we’re
really missing the slash that should start the end tag
</LinearLayout>
. From the editor’s syntactical
point of view, it knows only that it expected to find a
</LinearLayout>
before the next
<LinearLayout>
tag or the end of the file, and it
didn’t find one. The error message is enough to cause us to look in
the right place and figure out what is really wrong.
Now that we have main.xml
fixed up, let’s look at the first part of DebugTest.java as it appears in Eclipse
(Figure 5-5). We can see from
the right scroll margin that there are a total of seven errors, and
our mouse is hovering over the error in the declaration of
btnGo
.
Now for one of my favorite features of Eclipse. The source of the error displayed in Figure 5-5, it turns out, is that EditText can’t be resolved in the example, because we haven’t imported the package that defines EditTexts. You could go look in the Android documentation and find the right name for the library, but Eclipse has a labor-saving feature that will find it for you. Just type Ctrl-Shift-O (that’s the letter O) while the editor has focus, and Eclipse will attempt to resolve all the unresolved references in the file by finding appropriate libraries. With that one stroke, the appropriate packages get imported for EditText, Button, and WebView (you can’t see them in Figure 5-5, because they’re hidden by the pop up), and those errors disappear from the editing pane as well.
That leaves us with five more errors, so we scroll down as shown in the Eclipse screenshot in Figure 5-6.
The four errors in lines 29, 32, 33, and 35 have the same source as the one in Figure 5-2 and will go away the first time we build the project with the new main.xml. Let’s fix the remaining error using Eclipse’s help.
We currently have the mouse hovering over the error in line
19, and the pop up says we’re trying to pass an Editable instead of
a String to the loadURL(String)
method. That’s easy to fix: Editables have a toString
method, like most objects, so we
can change onClick
to look like
this:
public void onClick(View v) { try { wvBrowser.loadUrl(txtURL.getText().toString()); } catch (Exception e) {} }
Now we try to build and run the project (Run → Run → Android Application), but Eclipse tells us we still have errors. It helpfully lists all the problems found in the Problems tab, located in the pane at the bottom of the Eclipse window. Figure 5-7 shows that tab.
Clicking on an error in the Problems tab takes us directly to
the corresponding line of source code in the Editing pane for
DebugTest.java. A quick look at
main.xml reveals the problem:
we referred to the text box as URL
in main.xml, and tried to find it as
txtURL
in the Java code. A quick
fix to main.xml, and the
compile completes.
Eclipse starts the Android emulator for us and loads our application so it appears on the screen. The application runs—now to see whether it produces correct results.
If you type in a URL like www.oreilly.com
and click the Go button,
you get...an error. Instead of the web page you asked for, you see a
page that says “Web Page not Available.” Let’s try http://www.oreilly.com
...ah, that works.
So let’s add code that checks whether the URL starts with http://
, and if not, adds it:
public void onClick(View v) { try { String sURL = txtURL.getText().toString(); if(sURL.substring(0,6).equals("http://")) { wvBrowser.loadUrl(sURL); }else{ sURL = "http://" + sURL; wvBrowser.loadUrl(sURL); } } catch (Exception e) {} }
Now when we run the program using www.oreilly.com
as the URL, it works—but
http://www.oreilly.com
doesn’t!
Let’s use the debugger to figure out why.
The Android SDK makes the use of the Eclipse debugger completely transparent, so let’s use it to see what’s going wrong with our program. We’ll put a breakpoint at the line we just entered, so the debugger will break there when we run the program. Eclipse gives us three ways to toggle a breakpoint:
Use the menus. Select the line you want to toggle and choose Run → Toggle Breakpoint.
Use the keyboard. Select the line you want to toggle and key Ctrl-Shift-B.
Double-click in the left margin of the editor window at the line you want to toggle (my favorite method).
Whatever way you choose, you end up with a breakpoint mark in the left margin of the editor window, as shown in Figure 5-8.
To invoke the Debugger, choose Run → Debug → Android Application from the Eclipse menu. Eclipse and the Android SDK do what they did before (build the program if necessary, convert to Dalvik, invoke the emulator, load your program, and start it running). You may get a window in the Emulator that says “Waiting for Debugger: Application DebugTest is waiting for the Debugger to connect.” If you do, just wait a few seconds and the Debugger should finish initializing, the window will disappear, and you’ll see the DebugTest screen.
Now enter http://www.oreilly.com
and click the Go
button. DebugTest starts executing and breaks at the breakpoint.
Eclipse automatically changes to the Debug Perspective, showing you panes that
apply to debugging your application. Starting from the upper left
and moving down the window, left to right, these are:
The Debug pane has a single tab (Debug) that shows a trace of recent execution. It should show that you are at a breakpoint in a Dalvik thread running DebugTest, at Java line 19. In its toolbar, this pane also contains the buttons for Resume, Suspend, Terminate, Step Into, Step Over, Step Return, etc.
This pane has two tabs, the most useful of which is
Variables, where you can see the current value of variables
that are in scope. So far it’s showing values for this
and v
.
This contains a tab for each of the source files that you had open in the Java Perspective. The currently displayed tab should show DebugTest.java, highlighting the current breakpoint (line 19).
This shows the structure of your application.
DebugTest is simple, so it shows only one method, OnCreate
.
This pane has tabs for each of these views, which don’t contain much that’s interesting at the moment. The Console is the most useful, and in some debug situations can have important information telling you what is (or isn’t) happening.
This is the subject of the next section: the contents of the Android logcat log, with buttons to filter the content.
Focusing on the Editor pane, which shows us stopped at line
19, let’s use the Step Over button (in the Debug toolbar in the pane
above) to step the program one line, to line 20. Now sURL
appears in the Variables Pane, and it
has the right value, http://www.oreilly.com
. Step once more and
you can tell something’s wrong: we expected the program to take the
first branch of the if
, and it
took the second instead. That’s why http://
is appearing twice in the URL
string. If we step once more we can see that, as the value of
sURL
changes in the Variables
Pane.
To find out why, let’s use another debug feature of Eclipse.
From the menu, choose Window → Show View → Display. A new Display
tab is added to the lower-left pane, and comes to the front. As long
as the Debugger is running, you can type any variable or expression
that’s in scope into this window to display the variable’s value or
execute the expression. We should be curious about the expression
we’re comparing the user’s URL to, sURL.substring(0,6)
. So cut and paste this
method call from the Editor pane into the Display tab, select the
expression, right-click, and choose Display from the pop-up menu.
Eclipse evaluates the expression and displays the result in the
pane—and what do you know, it’s http:/
, with the last /
missing, as shown in Figure 5-9. This problem may be
typical of errors that programmers encounter with the use of Java’s
substring
method, because its
second parameter represents the location of the last character, not
the count of characters, as in some other languages. We change the 6
to 7, and the program works fine.
Granted, the errors we debugged in the last section were pretty straightforward—no different from debugging in any other environment. But most applications are not as simple as DebugTest, and many problems are much harder to isolate and solve. Android provides a general-purpose logging facility that can help with many of those more difficult problems.
As mentioned before, there’s a logcat pane on the Debug perspective (it’s also in the DDMS perspective, which we’ll talk about in the next section and in DDMS: Dalvik Debug Monitor Service). The log for DebugTest isn’t very interesting, so instead start MJAndroid in Debug mode and we’ll take a look at its log.
After the application comes up in the emulator, Eclipse switches to the Debug Perspective and shows the logcat pane on the lower right, as it looks in Figure 5-10.
To make the pane large enough to be useful for reading the log, click on the “full screen” symbol at the upper right of the pane, and it will expand to fill the Eclipse window. You will then see that there are hundreds of log messages in the file, going back to when Eclipse first started the instantiation of the emulator that you are using, continuing through the boot process for Android, loading all the applications, and finally loading and executing MJAndroid. How are you supposed to find anything useful in all of that?
Luckily, Android provides you with some handy filters to apply to the logfile. See the V, D, I, W, and E symbols in the toolbar? These filters successively narrow the scope of messages displayed, as follows:
Show everything
Show Debug, Information, Warning, and Error messages (equivalent to V for now)
Show Information, Warning, and Error messages
Show Warning and Error messages
Show only Error messages
The columns displayed for the log are:
The time the log entry was made
One of the log entry types from the previous list (D, I, W, or E)
The Linux process ID of the process making the entry
A short tag describing the source of the entry
The log entry itself
About two-thirds of the way through the log (if you started a new emulator when you brought up MJAndroid), you’ll see a message entry something like:
11-28 12:10:31.475: INFO/ActivityManager(52): Start proc com.microjobsinc.mjandroid for activity com.microjobsinc.mjandroid/.MicroJobs: pid=163 uid=10017 gids={3003}
which actually appears all on one line; we’ve broken it here so it will fit on a printed page.
This is a log message from the Activity Manager telling us that it started MicroJobs with process ID 163 (it will probably be different as you run it). If you click on the green cross at the top of the logcat pane, it will let you define a custom filter. Fill in a random name and the pid number that you saw in the log. Now the log is filtered to show only the messages that apply to this instance of MicroJobs. There are likely still a lot of messages, which you can filter further (using the D, I, W, and E buttons) or just scan.
If you ask other people for help debugging an error in your own program, one of the first things you’ll likely be asked for is a copy of the logcat output. You can easily extract the content of the logfile to a text file by selecting what you’d like to preserve and clicking on the little down arrow at the upper right of the logcat pane, which brings down a pull-down menu. One of the selections on the menu is “Export Selection as Text...”, which takes you to a dialog where you can name an output file for the log text.
Logcat gives you a lot of information about what happened as Android tried to run your program. It is very useful when you get a generic error message from Android that doesn’t tell you much. Let’s demonstrate one of my (least) favorites.
In Eclipse, go to main.xml for MJAndroid and remove the apiKey line under the MapView declaration (save it in a text file or somewhere, so you can restore it; we’re doing this just to generate an error). The apiKey is needed to access mapping information, so removing it brings the program to a screeching halt. When you run the program, the emulator screen looks like Figure 5-11.
Although it’s good to know that the application stopped, the message tells us very little about why. If you now look at the logcat output in the Debug perspective (or the DDMS perspective), you’ll find something like this after MicroJobs starts up, all in red type (we’ve left off the first few columns so it will fit):
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.microjobsinc.mjandroid/com.microjobsinc.mjandroid.MicroJobs}: android.view.InflateException: Binary XML file line #8: Error inflating class java.lang.reflect.Constructor at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2140) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2156) at android.app.ActivityThread.access$1800(ActivityThread.java:112) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1580) at android.os.Handler.dispatchMessage(Handler.java:88) at android.os.Looper.loop(Looper.java:123) at android.app.ActivityThread.main(ActivityThread.java:3742) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497) at dalvik.system.NativeStart.main(Native Method) Caused by: android.view.InflateException: Binary XML file line #8: Error inflating class java.lang.reflect.Constructor at android.view.LayoutInflater.createView(LayoutInflater.java:512) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:564) at android.view.LayoutInflater.rInflate(LayoutInflater.java:617) at android.view.LayoutInflater.inflate(LayoutInflater.java:407) at android.view.LayoutInflater.inflate(LayoutInflater.java:320) at android.view.LayoutInflater.inflate(LayoutInflater.java:276) at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:227) at android.app.Activity.setContentView(Activity.java:1569) at com.microjobsinc.mjandroid.MicroJobs.onCreate(MicroJobs.java:132) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1122) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2103) ... 11 more Caused by: java.lang.reflect.InvocationTargetException at com.google.android.maps.MapView.<init>(MapView.java:227) at java.lang.reflect.Constructor.constructNative(Native Method) at java.lang.reflect.Constructor.newInstance(Constructor.java:424) at android.view.LayoutInflater.createView(LayoutInflater.java:499) ... 21 more Caused by: java.lang.IllegalArgumentException: You need to specify an API Key for each MapView. See the MapView documentation for details. at com.google.android.maps.MapView.<init>(MapView.java:263) at com.google.android.maps.MapView.<init>(MapView.java:244) ... 25 more
The first three errors basically tell us that Android could not start our application because it could not inflate the Views it found in our layout file. The last error block we showed in the output clearly tells us we need an API Key for each MapView. Logcat is often the best way to get insight into errors where there isn’t specific information otherwise.
To write your own entries from your application into logcat, Android provides methods corresponding to the different entry priorities. The methods are all of the form:
Log.x
(Stringtag
, Stringmessage
, [Throwableexception
])
where x
can be v
, d
,
i
, w
, or e
, and the optional exception makes it
easy to report exceptions that you didn’t anticipate in your code
but encounter within a try/catch block. For example, look at the
onItemSelected
method for the
Spinner
in MicroJobs.java:
try { mc.animateTo(mMyLocationOverlay.getMyLocation()); } catch (Exception e) { Log.i("MicroJobs", "Unable to animate map", e); } mvMap.invalidate();
Android comes with a specialized command-line debug utility
called adb
. It lets you control a
device or emulator from your host, offering the kind of remote
terminal or remote shell service that embedded programmers have come
to expect when working with their target systems. Invoke the
adb
client from a command
prompt on the host (Start → Run → cmd.exe on Windows, or open a
terminal window on Linux or OS X). The client talks to an adb
server that runs in background on the
host and processes requests. If the server isn’t running when you
start the client, it starts the server for you. The server in turn communicates with
adb
daemons that run on either a
device or an emulator. All of this communication is through TCP/IP
ports. A single client/server can deal with multiple devices and
emulators, but to simplify things for our discussion, we’ll assume
there’s only one.
If you just type adb
at
the command prompt, you get the help information for adb
:
Android Debug Bridge version 1.0.20 -d - directs command to the only connected USB device returns an error if more than one USB device is present. -e - directs command to the only running emulator. returns an error if more than one emulator is running. -s <serial number> - directs command to the USB device or emulator with the given serial number -p <product name or path> - simple product name like 'sooner', or a relative/absolute path to a product out directory like 'out/target/product/sooner'. If -p is not specified, the ANDROID_PRODUCT_OUT environment variable is used, which must be an absolute path. devices - list all connected devices device commands: adb push <local> <remote> - copy file/dir to device adb pull <remote> <local> - copy file/dir from device adb sync [ <directory> ] - copy host -> device only if changed (see 'adb help all') adb shell - run remote shell interactively adb shell <command> - run remote shell command adb emu <command> - run emulator console command adb logcat [ <filter-spec> ] - View device log adb forward <local> <remote> - forward socket connections forward specs are one of: tcp:<port> localabstract:<unix domain socket name> localreserved:<unix domain socket name> localfilesystem:<unix domain socket name> dev:<character device name> jdwp:<process pid> (remote only) adb jdwp - list PIDs of processes hosting a JDWP transport adb install [-l] [-r] <file> - push this package file to the device and install it ('-l' means forward-lock the app) ('-r' means reinstall the app, keeping its data) adb uninstall [-k] <package> - remove this app package from the device ('-k' means keep the data and cache directories) adb bugreport - return all information from the device that should be included in a bug report. adb help - show this help message adb version - show version num DATAOPTS: (no option) - don't touch the data partition -w - wipe the data partition -d - flash the data partition scripting: adb wait-for-device - block until device is online adb start-server - ensure that there is a server running adb kill-server - kill the server if it is running adb get-state - prints: offline | bootloader | device adb get-product - prints: <product-id> adb get-serialno - prints: <serial-number> adb status-window - continuously print device status for a specified device adb remount - remounts the /system partition on the device read-write networking: adb ppp <tty> [parameters] - Run PPP over USB. Note: you should not automatically start a PDP connection. <tty> refers to the tty for PPP stream. Eg. dev:/dev/omap_csmi_tty1 [parameters] - Eg. defaultroute debug dump local notty usepeerdns adb sync notes: adb sync [ <directory> ] <localdir> can be interpreted in several ways: - If <directory> is not specified, both /system and /data partitions will be updated. - If it is "system" or "data", only the corresponding partition is updated.
Here are a few of the more useful adb
commands. There is much more
information about these and other adb
commands in the Android
documentation and online.
adb devices
Displays a list of devices and emulators that the
adb
server knows about.
This is a good way to find the TCP/IP port for an emulator or
device if you don’t already know it. The port number is also
displayed in the title of each emulator at the top of its
window. If there’s only one device or emulator running (the
normal case, unless you’re debugging a multidevice
application), any adb
commands you issue automatically go to that target. The
-s
and -e
options are
provided for multidevice applications to let you specify a
device or emulator.
adb shell
This connects you with a shell running on the target and
gives you a #
prompt. The shell is a simplified Unix-like shell, so
you can use the usual shell commands (ls
, cat
, rm
, ps
, etc.) to explore the target
and make changes as appropriate. Ctrl-D or exit
will get you out of the shell
and back to your environment on the host.
sqlite3
[path_to_database
]A particularly useful shell command (you have to get into the shell with
adb shell
first) for
manipulating SQLite
database files. The sqlite3 program is
further described in Chapter 8, and on the SQLite
website (http://www.sqlite.org). You can
optionally include the path to the database file you want to
manipulate (the
MJAndroid database, for example, would be in
data/data/com.microjobsinc.mjandroid/databases/MJAndroid
).
adb
logcat
[filter_spec
]This is another way of looking at the logcat log on the target. When
you run it, it dumps the existing log to your virtual terminal
and continues to send additional log entries as they are
generated in the running system. The command is normally
entered with a trailing &
, the Unix parameter for “run this in a separate
process,” so that you can go on and use the terminal for other
commands (including, eventually, to kill the logcat process).
The filter specs are of the form
tag:priority
, where tag
and
priority
were described in Logcat. So the command to
see all AndroidRuntime
log entries of
priority E would be:
adb
logcat
AndroidRuntime:E &
This is also useful for reading the “other” logs, of which there are two: radio and events. The radio log is accessed through a command like:
adb -b
radio &
Similarly, to read the events log, enter:
adb -b
events &
adb install
[-l
] [-r
]
file_spec
This can be used to install or reinstall an
application. The -l
option forward-locks the
installation (preventing the application from being copied
later to another device), and the
-r
option reinstalls the application without
overwriting the existing
application data. The file_spec
must be a valid, signed .apk file for the application to be
installed.
adb uninstall
[-k
]
package
This uninstalls the application with the given
package name. The package
parameter
needs to be the full name of the package, without the
“.apk”
extension. So to uninstall MicroJobs, for example, you’d
type:
adb uninstall
com.microjobsinc.mjandroid
If you want to keep the application’s associated data,
you include the -k
option.
adb push
local
remote
This command copies a file from the
local
name on the host to the
remote
name on the target.
adb pull
remote local
This is the counterpart to the previous command, and copies a file from the target to the host.
Installing the Android Software Development Kit adds DDMS to the Eclipse integrated development environment, providing a window-oriented interface to Android-specific debug information on the target. The most frequently used perspectives are displayed in the upper-right corner of the Eclipse window. If there’s a DDMS button there, you can just click on it to switch to DDMS. If not, in that same area there is a little window symbol with a + sign in its upper-right corner. Clicking on this window will open a menu of Perspectives, including DDMS.
The DDMS perspective has four panes by default. Starting from the upper left and going left to right down the screen, these are:
This lists the available target devices connected to Eclipse, and the processes running on each device. The default emulator device is labeled with its port number (5554). There are also some toolbar buttons in this pane, described later in this section.
This provides three different views of what is going on in the target. The Threads tab shows the currently active threads in the selected “client,” which is the application selected in the Devices pane. To see the Threads information, you have to click the “Update Threads” button at the top of the Devices pane. The Heap tab shows the state of the VM’s heap memory, and is updated at each garbage collect. Again, in order to see the Heap information, you need to enable it by clicking the “Update Heap” button at the top of the Devices pane, and you may need to exercise the application for a while until the VM decides a garbage collect is required before the information will be updated. You can also force a garbage collect by clicking on the “Cause GC” button in the Heap view.
This gives you control of the Telephony and Location emulation functions:
You can simulate voice and data operation in a variety of network states (unregistered, home, roaming, searching, denied) and at a variety of network speeds and latencies. It’s useful to vary these parameters during application testing to be sure that your application responds appropriately in all typical situations. You can also simulate incoming voice and SMS calls from a specific number (to test Caller ID), and create the SMS message to be received.
Here you can send a specific location fix to the Location Provider by entering a latitude and longitude. You can alternatively load a GPX or KML file of locations to be played back to the Location Provider in a continuous sequence, as though the target was moving around.
This is similar to the “catchall” pane in the Debug perspective, providing a collection of useful tabs that display the indicated information.
This isn’t a pane, but one of the toolbar buttons in the Display pane. It looks like a very small Android screen, and when you click it, it captures and displays what is currently showing on the target screen. It gives you the opportunity to save the capture to a PNG file, which you can then use as you would any other image.
Maybe the problem you’re trying to debug isn’t about functionality. Maybe your application does exactly what it’s supposed to do, but takes too long to do it. Wouldn’t it be nice to have a way of seeing how the methods within your classes are interacting, and even to keep track of the relative time spent executing in each method? Traceview is a utility that allow you just that kind of visibility. It consists of two parts, one that you enable before running your program and one that you work with after the run in order to diagnose your findings:
You can enable and disable logging for your application. While enabled, routines are linked into your application that create a binary trace file on the target. The trace file records every method instantiation and the time spent in each method.
If you then copy the binary trace file from the target to your host, you can run a trace analysis program that displays all the information from the file in graphical form. You can easily observe which methods are consuming most of the runtime, and drill down into those methods to find out which methods they in turn call and which of them consume the most time.
The routines to perform trace data collection are provided in the Android Software Development Kit. All you have to do is:
The tracing routines always write their trace information to a file on the target’s SD card. If you’re running on a real device, you need to plug in an SD card. If you’re debugging on the emulator, you need to create a virtual SD card and tell the emulator to use it:
Create a virtual SD card with mksdcard
.
From the host command prompt, use the mksdcard
utility to create a file
that the emulator can use as a virtual SD card:
$mksdcard -l ANDROID 1024M
filename
You can create the file anywhere you like, but the root
directory for your project is a good place. The utility will
allocate a file as big as the size you’ve given in the
mksdcard
command (1 GB
in the example shown).
Tell the emulator to use the virtual SD card.
In Eclipse, choose Window → Preferences → Android → Launch. You’ll see a box there for emulator options. Add the following option:
-sdcard filename
Use the complete path to the file, so the emulator can always find it, no matter where it’s running from.
As an example of the code needed, let’s add tracing to MicroJobs and collect some data. We add tracing to MicroJobs.java as follows:
... import android.os.Debug; ... public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // start trace Debug.startMethodTracing("x"); ... // stop tracing when application ends @Override public void onDestroy() { super.onDestroy(); Debug.stopMethodTracing(); }
Running MJAndroid now creates a file named x.trace on the virtual SD card on the target. When tracing is enabled, the Dalvik virtual machine is noticeably slower to start up and slower to run, because it is mapping the virtual SD card into memory, and collecting all the method call and timing data for you as it runs. For this example we went through a few UI operations and then closed the application.
To analyze x.trace, move it back to the host:
$ adb pull sdcard/x.trace x.trace
and start the Traceview program:
$traceview
pathname
x.trace
For the moment at least, Traceview expects the full pathname of the trace file.
You are rewarded with a display of all the methods that were called between the time you started and stopped the trace—not just the methods in your application, but all the methods that were called. The top part of the display is the Timeline Panel, which looks something like Figure 5-12. The numbered line across the top is a timeline (in milliseconds), with each application thread listed as a separate row. Within each row, each method invocation is shown as a little colored block (a little hard to see at the startup resolution). The colors map to a list of methods shown in Figure 5-12.
You can zoom in on a region of interest by moving the mouse into the timeline area, clicking the left mouse button at the start time of interest, dragging to the stop time, and releasing the button. The timeline then zooms in, as shown in Figure 5-13. As you move the mouse from left to right, the timeline cursor shows the sequence of method calls, and the method names are called out in the upper right.
The bottom part of the Traceview display lists each method, in declining order by the amount of time spent in it. The first part of that list is shown in Figure 5-14.
The columns in this display have the following meanings:
You can’t see colors here, but on the screen, the color in the color-coded box to the left of each name tracks to the timeline shown in Figure 5-12. The 15 colors get reused in order by inclusive time, as you go down the list.
The time (and percentage of total time) spent in this method, including all the methods that it called. The times are in milliseconds, but they should be interpreted with care. Because tracing slows down execution considerably, these times do not represent the true runtimes under normal execution. They do provide accurate relative timing information when comparing the runtimes of two methods.
The time (and percentage of total time) spent actually executing in this method. In other words, any time spent in nested functions is removed from these two fields. The same timing caveats apply to Exclusive times as to Inclusive.
Two values: the number of times this method was called externally and the number of times it called itself.
Simply the quotient of the second column divided by the sum of the numbers in the sixth column.
When you select a method by clicking on its name in the Profile Panel, Traceview adjusts the pane to bring that method to the top of the view, and opens a list of Parent and Child methods, as shown in Figure 5-15. “Parents” are methods that call this method. “Children” are methods called by this method.
Clearly, there is a lot of information available in the
Traceview records, and a full exploration is beyond the scope of
this book. We’ll leave other features of Traceview for you to
explore, such as the use of Native Tracing to trace the QEMU
emulator itself, the use of the other Debug methods to get timing
information, and the use of the dmtracedump
utility to generate call
graphs.
Debugging and profiling are large topics within themselves, and we have only scratched the surface of the tools and methods available to you to develop Android applications. Some references follow to other sources of information that might prove useful:
Debugging with the Eclipse Platform, http://www.ibm.com/developerworks/library/os-ecbug/
For information about using the platform, http://www.eclipse.org
Debugging Tasks (part of the Android SDK documentation), http://d.android.com/guide/developing/debug-tasks.html
Developing on a Device (part of the Android SDK documentation), http://d.android.com/guide/developing/device.html
Using Dalvik Debug Monitoring Service (DDMS) (part of the Android SDK documentation), http://d.android.com/guide/developing/tools/ddms.html
Traceview: A Graphical Log Viewer (part of the Android SDK documentation), http://d.android.com/guide/developing/tools/traceview.html
35.171.45.182