Monday, 6 June 2011

More Android command-line fun

Following on from yesterday’s post about building Android apps at the command-line, this post looks at more things you can do with the command-line tools

You might normally start up an Android emulator (or Android Virtual Device) by running android.bat. This invokes the Android SDK and AVD manager, but unlike when you run SDK Manager.exe, it does not immediately connect to the network and update the list of available SDK packages. Instead it starts on the Virtual devices page and lets you create, edit or start an emulator.

Android SDK and AVD Manager

If you can remember the name of the emulator you want to start, say Android_2.2, then you can invoke it directly with:

emulator -avd Android_2.2

or, even simpler:

emulator @Android_2.2

Most interaction with the emulator or the device is done with the Android Debug Bridge (adb.exe). As mentioned in the last post, running adb devices will enumerate all connected devices and emulator instances and list out their serial numbers and connected state. Any emulators have the word emulator in their serial number to make it easy to spot them.

On occasion I've had problems with adb failing to notice an emulator that has been started up after initially working with a real Android device. This is usually overcome by telling adb to remove itself from memory and reload. adb acts like a daemon and normally stays running in memory between invocations, so to kill it you can use:

adb kill-server

The next time you run an adb command the daemon will be re-started. Note that killing the daemon also gets over problems where the adb in-memory process retains a handle on an Android package that's been installed. I've bumped into the issue several times, where trying to move or delete an Android project directory has been thwarted because something has a handle open on a file in the project directory tree. This often seems to be adb and killing it frees the handle.

It's sometimes useful to be able to copy a file to or from the Android device. Sometimes this can be achieved by getting the phone to expose its SD card to your computer as a drive when connected, although this only exposes the SD card, and also while connected as an external drive any apps that have been installed to the SD card will not be available to run. An alternative option is to make use of the push and pull commands. For example:

C:\Users\Brian>adb -d push FileOnThePC.txt /sdcard/file_on_the_device.txt
0 KB/s (6 bytes in 0.141s)
C:\Users\Brian>adb -d shell ls /sdcard/fil*
/sdcard/file_on_the_device.txt
C:\Users\Brian>adb -d pull /sdcard/file_on_the_device.txt FileBackOnThePC.txt
0 KB/s (6 bytes in 0.008s)

adb allows you to run a shell (a Linux ash shell) on the emulator or physical device and run Unix command, based on what is available on the device. For space and practicality reasons there is a limited number of available commands as compared to a regular Linux system but the shell can be used in one of two ways. You can either invoke the command via the shell, have it execute and then be returned to the command prompt or you can enter the shell in interactive mode and manually execute as many commands as you wish.

So, for example, to see the contents of your phone's SD card main directory you can run this one-shot command:

adb -d shell ls -l /sdcard/

This invokes a temporary shell, lists the contents of the /sdcard directory in long (detailed) format, then exits back to the command prompt. Alternatively you can run:

adb -d shell

and you will enter an interactive shell session. Now you can run multiple shell commands on your phone (or use -e to run on the emulator, or –s serial_no to specify a particular device). The exit command quits the shell and returns to the command prompt, as does pressing Ctrl+C.

If you want to find the available commands while in a shell you can look in /system/bin as that's where the command binaries live. However, my advice is be very cautious experimenting with unfamiliar commands, especially on your phone, and even more so if you have rooted your phone.

In general there is no actual need to terminate an Android application, as they normally die as and when appropriate under the control of the OS. But sometimes we need to help the system along, for example we may have written a service that won't exit. Under circumstances such as this we can use shell commands to help out. You can get a list of all running processes on a phone, including the name and the numeric process ID (PID) with:

adb -d shell ps

Once you've identified the rogue process you can then terminate it with:

adb -d shell kill PID

The shell command getprop can list out a whole swathe of Android kernel parameters or just a named parameter. On my Android device in Settings, About phone, there is a Software information page that lists out various internal information. I can get much of this same information out using getprop:

C:\Users\Brian>adb -d shell
$ getprop gsm.version.baseband
getprop gsm.version.baseband
32.49.00.32U_5.11.05.27
$ getprop ro.build.description
getprop ro.build.description
2.29.405.5 CL293415 release-keys
$ getprop ro.product.version
getprop ro.product.version
2.29.405.5
$ exit
exit
C:\Users\Brian>

To get a list of all the packages installed on a device you can use the pm command. The basic package list command lists out all the package names, such as com.android.alarmclock: command

adb -e shell pm list packages

Using the -f command adds into the list the path on the device to the package file name (the .apk file).

adb -e shell pm list packages -f

adb also allows us to work with three types of Android application components: activities, broadcast receivers and services. Each of these can be stimulated with an intent, which is passed in the command. Intents are quite flexible in their specification and can include an action (-a flag) with an optional data URI (-d flag), one or more categories (-c flag), a component name (-n flag) and more besides.

To start an activity, broadcast an intent or start a service you can use the shell am command:

adb [–d|-e|-s serial_no] shell am start INTENT
adb [–d|-e|-s serial_no] shell am broadcast INTENT
adb [–d|-e|-s serial_no] shell am startservice INTENT

So to start an application you could send your main activity the same intent that Android does. The intent would need an action of android.intent.action.MAIN, optionally a category of android.intent.category.LAUNCHER, and specify the main activity as its component. This is reflecting how the main activity is identified in your AndroidManifest.xml. To identify the activity you use package_name/class_name, again as per the content of AndroidManifest.xml. So a command-line to start a Torch example application might look like:

adb –d shell am start –a android.intent.action.MAIN -n com.blong.Torch/com.blong.torch.MainActivity

As another example of using am, let's say you have a service that has been designed to start when the Android device boots up. This would have been implemented using a broadcast receiver set up to respond to an intent with an action of android.intent.action.BOOT_COMPLETED. If you were testing it and didn't want to reboot the device, you could simulate a reboot for the broadcast receiver's benefit, using a command like:

adb shell am broadcast -a android.intent.action.BOOT_COMPLETED -c android.intent.category.HOME -n package_name/class_name

Among other commands available to the shell is a SQLite command shell. If you use a SQLite database in your application use can use the SQLite command shell to analyse and query your database. However it should be noted that this only works in the emulator and on rooted phones - by default the location of SQLite databases is not accessible.

So, for a database called SampleApp created in an app called com.blong.SampleApp, these commands can work on the database:

C:\Users\Brian>adb -e shell
# sqlite3 /data/data/com.blong.SampleApp/databases/SampleApp
sqlite3 /data/data/com.blong.SampleApp/databases/SampleApp
SQLite version 3.6.22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
.tables
Customers         android_metadata
sqlite> .schema Customers
.schema Customers
CREATE TABLE Customers (_id integer primary key autoincrement, FirstName text not null, LastName text not null, Town text);
sqlite> .dump Customers
.dump Customers
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE Customers (_id integer primary key autoincrement, FirstName text not null, LastName text not null, Town text);
INSERT INTO "Customers" VALUES(1,'John','Smith','Manchester');
INSERT INTO "Customers" VALUES(2,'John','Doe','Dorchester');
INSERT INTO "Customers" VALUES(3,'Fred','Bloggs','Winchester');
INSERT INTO "Customers" VALUES(4,'Walt','Jabsco','Ilchester');
INSERT INTO "Customers" VALUES(5,'Jane','Smith','Silchester');
INSERT INTO "Customers" VALUES(6,'Raymond','Luxury-Yacht','Colchester');
COMMIT;
sqlite> select * from Customers where FirstName = 'John';
select * from Customers where FirstName = 'John';
1|John|Smith|Manchester
2|John|Doe|Dorchester
sqlite> .quit
.quit
# exit
exit
C:\Users\Brian>

Finally for the shell, it's useful to know of the Android Monkey. This is a testing tool that pseudo-randomly clicks, swipes, types and generally monkeys around in your app, to allow you to find problem areas with unforeseen combinations of actions. It also introduces some system-level events into the mix.

The Monkey stress-tests your application but can repeat any series of actions to allow proper testing. It notices exceptions, ANRs and (optionally) ensures its activity is constrained to your own package to ensure things don't go very bad on your device.

The following command line runs the Monkey over a sample application on the emulator whose package name is specified. It will perform 500 pseudo-random events and its output will be at Level 2 verbosity (thanks to the 2 occurrences of the -v switch).

adb -e shell monkey -p com.blong.SampleApp -v -v 500

Amusingly enough (or perhaps helpfully enough), Monkey managed to crash my sample application after a mere 47 injected events, so I guess I'd better review the code there before it gets passed on to anyone!

Some other possibly useful command-line invocations, outside of adb, are as follow.

To get a list of what's in an Android package (an .apk file) you can use aapt.exe:

C:\Users\Brian>aapt l com.blong.SampleApp.apk
META-INF/MANIFEST.MF
META-INF/ANDROIDD.SF
META-INF/ANDROIDD.DSA
res/drawable/splash.png
res/layout/about.xml
res/layout/db.xml
res/layout/googlemap.xml
res/layout/gps.xml
res/layout/gpsandgoogle.xml
res/layout/info.xml
res/layout/listitem_twolines.xml
res/layout/listview_header.xml
res/layout/main.xml
res/layout/splash.xml
res/layout/webbrowser.xml
AndroidManifest.xml
resources.arsc
res/drawable-hdpi/icon.png
res/drawable-ldpi/icon.png
res/drawable-mdpi/icon.png
classes.dex

aapt is also useful for checking the required permissions of an Android package and finding what display hardware is supported by alternate resources:

C:\Users\Brian>aapt d badging com.blong.SampleApp.apk
package: name='com.blong.SampleApp' versionCode='' versionName=''
application: label='Sample App' icon='res/drawable-mdpi/icon.png'
uses-library:'com.google.android.maps'
launchable activity name='com.blong.sampleapp.SplashActivity'label='Sample App' icon=''
sdkVersion:'4'
uses-permission:'android.permission.INTERNET'
uses-permission:'android.permission.ACCESS_NETWORK_STATE'
uses-permission:'android.permission.ACCESS_FINE_LOCATION'
uses-permission:'android.permission.ACCESS_COARSE_LOCATION'
uses-feature:'android.hardware.location'
uses-feature:'android.hardware.location.gps'
uses-feature:'android.hardware.location.network'
uses-feature:'android.hardware.touchscreen'
main
other-activities
supports-screens: 'small' 'normal' 'large'
supports-any-density: 'true'
locales: '--_--'
densities: '120' '160' '240'

I hope these might prove useful for you whilst working with Android. I'll be back with more Android miscellanea in the future.

2 comments: