跳转至

工具和命令

一旦框架层和基本应用集启动并运行,有相当多的命令可用于查询系统服务或与之交互。与第 6 章中介绍的命令非常相似,这些命令可以在你 shell 登录设备后从命令行使用。但是,这些命令在框架层不运行时没有意义,因此也没有效果。当然,在你在新设备上启动 Android 和/或在新设备上调试框架层的某些部分时,你会发现其中许多命令非常有用,甚至至关重要。而且与第 6 章中的命令一样,用于与框架层交互的工具在文档和功能方面差异很大。但它们提供了在新硬件上启动 Android 或对现有产品进行故障排除所需的基本功能。让我们看看可用于与 Android 框架层交互的命令集。

这些命令中有许多位于 AOSP 源码的 frameworks/base/cmds/ 目录中,不过在 4.2/Jelly Bean 中,其中一些命令已移至 frameworks/native/cmds/。我鼓励你在使用其中一些命令时参考这些源码,因为它们的效果并不总是通过在线帮助显而易见的。

通用工具

与我们后面将看到的一些工具不同,某些工具对于与框架层的各个部分进行交互非常有用。其中一些非常强大。

service

service 命令允许我们与向服务管理器注册的任何系统服务进行交互:

# service -h
Usage: service [-h|-?]
       service list
       service check SERVICE
       service call SERVICE CODE [i32 INT | s16 STR] ...

选项: - i32:将整数 INT 写入发送的包中。 - s16:将 UTF-16 字符串 STR 写入发送的包中。

如你所见,它既可以用于查询,也可以用于调用系统服务的方法。以下是 2.3/Gingerbread 中用于查询现有系统服务列表的方法:

# service list
Found 50 services:
0 phone: [com.android.internal.telephony.ITelephony]
1 iphonesubinfo: [com.android.internal.telephony.IPhoneSubInfo]
2 simphonebook: [com.android.internal.telephony.IIccPhoneBook]
3 isms: [com.android.internal.telephony.ISms]
4 diskstats: []
5 appwidget: [com.android.internal.appwidget.IAppWidgetService]
6 backup: [android.app.backup.IBackupManager]
7 uimode: [android.app.IUiModeManager]
8 usb: [android.hardware.usb.IUsbManager]
9 audio: [android.media.IAudioService]
10 wallpaper: [android.app.IWallpaperManager]
11 dropbox: [com.android.internal.os.IDropBoxManagerService]
12 search: [android.app.ISearchManager]
13 location: [android.location.ILocationManager]
14 devicestoragemonitor: []
15 notification: [android.app.INotificationManager]
16 mount: [IMountService]
17 accessibility: [android.view.accessibility.IAccessibilityManager]
...

方括号中提供的接口名称允许你浏览 AOSP 源码,找到定义该接口的匹配的 .aidl 文件。

你还可以检查给定的服务是否存在:

# service check power
Service power: found

最有趣的是,你可以使用 service call 直接调用系统服务的 Binder 公开方法。为了做到这一点,首先需要理解该服务的接口。以下是 2.3/Gingerbread 中 frameworks/base/core/java/com/android/internal/statusbar/IStatusBarService.aidl 中的 IStatusBarService 接口定义(4.2/Jelly Bean 的接口名称相同,但 setIcon() 的原型已更改):

interface IStatusBarService
{
    void expand();
    void collapse();
    void disable(int what, IBinder token, String pkg);
    void setIcon(String slot, String iconPackage, int iconId, int iconLevel);
...

请注意,service call 实际上需要一个方法代码,而不是方法名称。要找到与方法名称匹配的方法代码,你需要查找根据接口定义由 aidl 工具生成的代码。以下是生成在 out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/com/android/internal/statusbar/ 中的 IStatusBarService.java 文件的相关片段:

static final int TRANSACTION_expand = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_collapse = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_disable = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
static final int TRANSACTION_setIcon = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
...

此外,frameworks/base/core/java/android/os/IBinder.java 中对 FIRST_CALL_TRANSACTION 有如下定义:

int FIRST_CALL_TRANSACTION  = 0x00000001;

因此,expand() 的代码是 1,collapse() 的代码是 2。因此,以下命令将导致状态栏展开:

# service call statusbar 1

而以下命令将导致状态栏收起:

# service call statusbar 2

这是一个非常简单的情况,动作相当明显,调用的方法不带任何参数。在其他情况下,你需要更仔细地查看系统服务的 API 并理解预期的参数。此外,请记住,系统服务的接口不一定通过 .aidl 文件公开。在某些情况下,如活动管理器,接口定义是直接硬编码到常规 Java 文件中而不是自动生成的。在基于 C 的系统服务的情况下,Binder 的封送(marshaling)和解封送(unmarshaling)都是直接在 C 代码中完成的。因此,除了在 AOSP 的 frameworks/ 目录中使用 grep 之外,还要在 out/target/common/ 中查找 FIRST_CALL_TRANSACTION 的所有实例。

dumpsys

另一件有趣的事情是查询系统服务的内部状态。实际上,每个系统服务在内部都实现了一个 dump() 方法,可以使用 dumpsys 命令进行查询:

dumpsys [ <service> ]

默认情况下,如果没有提供参数作为系统服务名称,dumpsys 将首先打印系统服务列表,然后转储它们的状态:

# dumpsys
Currently running services:
  SurfaceFlinger
  accessibility
  account
  activity
  alarm
  appwidget
  audio
  backup
  battery
  batteryinfo
  clipboard
  connectivity
  content
  cpuinfo
  device_policy
  devicestoragemonitor
  diskstats
  dropbox
  entropy
  hardware
...
-------------------------------------------------------------------------------
DUMP OF SERVICE SurfaceFlinger:
+ Layer 0x1e5788
      z=    21000, pos=(   0,   0), size=( 320, 480), needsBlending=0, needsDithering=0, invalidate=0, alpha=0xff, flags=0x00000000, tr=[1.00, 0.00][0.00, 1.00]
      name=com.android.internal.service.wallpaper.ImageWallpaper
      client=0x1ed2a8, identity=3
      [ head= 1, available= 2, queued= 0 ] reallocMask=00000000, identity=3, status=0
      format= 4, [320x480:320] [320x480:320], freezeLock=0x0, bypass=0, dq-q-time=2034 us
...

显然,输出非常冗长,最重要的是,需要理解相应系统服务的内部结构。但是,如果你正在实现你自己的系统服务,那么能够在运行时查询其状态可能至关重要。当然,如果你对转储所有系统服务状态不感兴趣,只需将你想要获取信息的特定服务的名称作为参数提供给 dumpsys:

# dumpsys power
Power Manager State:
  mIsPowered=true mPowerState=1 mScreenOffTime=46793204 ms
  mPartialCount=1
  mWakeLockState=SCREEN_ON_BIT
...

dumpstate

在某些情况下,你要做的可能是获取整个系统的快照,而不仅仅是系统服务。这就是 dumpstate 所负责的。实际上,你可能记得我们在第 6 章讨论 adb 的 bugreport 时提到过这个命令,因为 dumpstate 为 bugreport 提供其信息。以下是 2.3/Gingerbread 中 dumpstate 的详细帮助:

# dumpstate -h
usage: dumpstate [-d] [-o file] [-s] [-z]
  -d: append date to filename (requires -o)
  -o: write to file (instead of stdout)
  -s: write output to control socket (for init)
  -z: gzip output (requires -o)

在 4.2/Jelly Bean 中,dumpstate 的功能已扩展:

root@android:/ # dumpstate -h
usage: dumpstate [-b soundfile] [-e soundfile] [-o file [-d] [-p] [-z]] [-s] [-q]
  -o: write to file (instead of stdout)
  -d: append date to filename (requires -o)
  -z: gzip output (requires -o)
  -p: capture screenshot to filename.png (requires -o)
  -s: write output to control socket (for init)
  -b: play sound file instead of vibrate, at beginning of job
  -e: play sound file instead of vibrate, at end of job
  -q: disable vibrate

如果你不带任何参数调用它,它会继续查询系统的多个部分,以向你提供系统状态的完整快照。

在大多数情况下,正如你所看到的,dumpstate 实际上是在调用其他命令,如 logcat、dumpsys 和 ps 来检索其信息。如你所见,该命令非常冗长。

rawbu

在某些情况下,你可能希望备份 /data 的内容,以后再恢复它。你可以使用 rawbu 命令来做到这一点:

# rawbu help
Usage: rawbu COMMAND [options] [backup-file-path]

命令有:

  • help:显示此帮助文本。
  • backup:执行 /data 的备份。
  • restore:执行 /data 的恢复。

选项包括:

  • -h:显示此帮助文本。
  • -a:备份所有文件。

rawbu 命令允许你对 /data 分区执行低级备份和恢复。这是保存所有用户数据的地方,允许相当完整地恢复设备的状态。请注意,因为这是低级的,它只适用于相同(或非常相似)设备软件之间的构建。

以下是如何创建备份的方法:

# rawbu backup /sdcard/backup.dat
Stopping system...
Backing up /data to /sdcard/backup.dat...
Saving dir /data/local...
Saving dir /data/local/tmp...
Saving dir /data/app-private...
Saving dir /data/app...
Saving dir /data/property...
Saving file /data/property/persist.sys.localevar...
Saving file /data/property/persist.sys.country...
Saving file /data/property/persist.sys.language...
Saving file /data/property/persist.sys.timezone...
...
Backup complete!  Restarting system...

该命令做的第一件事是停止 Zygote,从而停止所有系统服务。然后继续从 /data 复制所有内容,最后重新启动 Zygote。

备份数据后,你可以稍后恢复:

# rawbu restore /sdcard/backup.dat
Stopping system...
Wiping contents of /data...
Restoring from /sdcard/backup.dat to /data...
Restoring dir /data/local...
Restoring dir /data/local/tmp...
Restoring dir /data/app-private...
Restoring dir /data/app...
...
Restore complete!  Restarting system, cross your fingers...

显然,正如命令输出所暗示的,这是一个脆弱的操作,你应该知道结果会因情况而异。

服务专用工具

正如我们之前看到的,有数十个系统服务。通常,使用这些系统服务需要编写以某种方式与它们的 Binder 公开 API 交互的代码。然而,在某些情况下,AOSP 包含用于直接与某些系统服务交互的命令行工具。其中一些工具非常强大,允许我们直接从命令行利用 Android 的功能。这为在生产或开发过程中将许多以下实用程序作为脚本的一部分使用打开了大门。

am

正如我之前提到的,最重要的系统服务之一是活动管理器。因此,有一个命令允许我们直接调用其功能,这是理所当然的。以下是 2.3/Gingerbread 中的在线帮助:

# am
usage: am [subcommand] [options]
    start an Activity: am start [-D] [-W] <INTENT>
        -D: enable debugging
        -W: wait for launch to complete
    start a Service: am startservice <INTENT>
    send a broadcast Intent: am broadcast <INTENT>
    start an Instrumentation: am instrument [flags] <COMPONENT>
        -r: print raw results (otherwise decode REPORT_KEY_STREAMRESULT)
        -e <NAME> <VALUE>: set argument <NAME> to <VALUE>
        -p <FILE>: write profiling data to <FILE>
        -w: wait for instrumentation to finish before returning
    start profiling: am profile <PROCESS> start <FILE>
    stop profiling: am profile <PROCESS> stop
    start monitoring: am monitor [--gdb <port>]
        --gdb: start gdbserv on the given port at crash/ANR
    <INTENT> specifications include these flags:
        [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]
        [-c <CATEGORY> [-c <CATEGORY>] ...]
        [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]
        [--esn <EXTRA_KEY> ...]
        [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]
        [-e|--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]
        [-n <COMPONENT>] [-f <FLAGS>]
        [--grant-read-uri-permission] [--grant-write-uri-permission]
        [--debug-log-resolution]
        [--activity-brought-to-front] [--activity-clear-top]
        ...

我们在第 2 章看到,应用开发者可以使用的四种组件类型是:activities、services、broadcast receivers 和 content providers。前三种类型的组件通过 intent 激活,而 am 的主要功能之一是能够直接从命令行发送 intent。

以下是你如何使用 am 让浏览器导航到给定网站以及相关的日志摘录:

# am start -a android.intent.action.VIEW -d http://source.android.com
Starting: Intent { act=android.intent.action.VIEW dat=http://source.android.com }
# logcat
...
I/ActivityManager(   62): Starting: Intent { act=android.intent.action.VIEW dat=http://source.android.com flg=0x10000000 cmp=com.android.browser/.BrowserActivity } from pid 786
I/ActivityManager(   62): Start proc com.android.browser for activity com.android.browser/.BrowserActivity: pid=794 uid=10015 gids={3003, 1015}
...
I/ActivityManager(   62): Displayed com.android.browser/.BrowserActivity: +1s924ms
...

这是一个相当简单的例子。让我们看一个稍微定制化的例子。以下是自定义应用的广播接收器声明:

<receiver android:name="FastBirdApproaching">
    <intent-filter >
         <action android:name="com.acme.coyotebirdmonitor.FAST_BIRD"/>
    </intent-filter>
</receiver>

以下是相应的代码:

public class FastBirdApproaching extends BroadcastReceiver {
  private static final String TAG = "FastBirdApproaching";
  @Override
  public void onReceive(Context context, Intent intent) {
    Log.i(TAG, "**********");
    Log.i(TAG, "Meep Meep!");
    Log.i(TAG, "**********");
  }
}

以下是你如何使用 am 触发这个广播接收器以及产生的日志输出:

# am broadcast -a com.acme.coyotebirdmonitor.FAST_BIRD
Broadcasting: Intent { act=com.acme.coyotebirdmonitor.FAST_BIRD }
Broadcast completed: result=0
# logcat
...
I/ActivityManager(   62): Start proc com.acme.coyotebirdmonitor for broadcast com.acme.coyotebirdmonitor/.FastBirdApproaching: pid=466 uid=10029 gids={}
I/FastBirdApproaching( 466): **********
I/FastBirdApproaching( 466): Meep Meep!
I/FastBirdApproaching( 466): **********
...

如你所见,通过 am 的在线帮助,你可以指定关于要发送的 intent 的很多细节。虽然前两个例子使用了隐式 intent,但你也可以发送显式 intent 来激活指定的组件:

# am start -n com.android.settings/.Settings

在这种情况下,这会启动系统设置应用的设置 activity。有趣的是,am 能够以你无法通过官方发布的 app 开发 API 复制的方式启动组件。那是因为它作为 AOSP 的一部分构建,因此可以访问仅对在 AOSP 内部构建的代码可用的隐藏调用。

am 实际上是一个 shell 脚本,如你可以在 frameworks/based/cmds/am/am/ 中看到的:

# Script to start "am" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/am.jar
exec app_process $base/bin com.android.commands.am.Am "$@"

该脚本使用 app_process 启动实现 am 功能的 Java 代码。命令行上传递的所有参数都按原样传递给 Java 代码。

你也可以使用 am 进行插桩(instrumentation)、性能分析(profiling)和监控。关于 Android 测试以及 am instrument 命令的使用,请参阅 Android 开发者手册的"测试基础"和"从其他 IDE 测试"部分。

am profile 命令允许我们生成数据,然后可以在主机上使用 traceview 命令进行可视化。你可以在 Android 开发者手册的相关部分找到有关 traceview 的更多信息。请注意,文档说有两种方法创建跟踪文件,而命令行上 am 命令的使用并未列为其中之一。

最后,am monitor 命令允许我们监控由活动管理器运行的应用。以下是我启动该命令然后启动多个应用的一个会话:

# am monitor
Monitoring activity manager...  available commands:
(q)uit: finish monitoring
** Activity starting: com.android.browser
** Activity resuming: com.android.launcher
** Activity starting: com.android.settings
** Activity resuming: com.android.launcher
** Activity starting: com.android.browser
** Activity starting: com.android.launcher
...

请注意,当你启动一个应用并点击返回时,命令报告启动器正在恢复,而如果你点击主屏幕按钮,则报告启动器正在启动。这种监控能力还可以让你捕获 ANR(应用无响应)并附加 gdb 到崩溃的进程。

不要让这个对 am 的简要介绍误导你:这是一个非常强大和有用的命令,你应该牢记在心。如果你需要从命令行编写启动应用的脚本,你会发现它非常有用。

pm

另一个非常重要的系统服务是包管理器,与活动管理器一样,它也有自己的命令行工具。以下是 2.3/Gingerbread 中的在线帮助:

# pm
usage: pm [list|path|install|uninstall]
       pm list packages [-f] [-d] [-e] [-u] [FILTER]
       pm list permission-groups
       pm list permissions [-g] [-f] [-d] [-u] [GROUP]
       pm list instrumentation [-f] [TARGET-PACKAGE]
       pm list features
       pm list libraries
       pm path PACKAGE
       pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f] PATH
       pm uninstall [-k] PACKAGE
       pm clear PACKAGE
       pm enable PACKAGE_OR_COMPONENT
       pm disable PACKAGE_OR_COMPONENT
       pm setInstallLocation [0/auto] [1/internal] [2/external]

list packages 命令打印所有包,可选地只打印其包名包含 FILTER 中文本的那些包。选项:

  • -f:查看它们的关联文件。
  • -d:过滤为包含禁用的包。
  • -e:过滤为包含已启用的包。
  • -u:还包括未安装的包。

list permission-groups 命令打印所有已知的权限组。

list permissions 命令打印所有已知的权限,可选地只打印 GROUP 中的那些。选项:

  • -g:按组组织。
  • -f:打印所有信息。
  • -s:简短摘要。
  • -d:仅列出危险权限。
  • -u:仅列出用户将看到的权限。

list instrumentation 命令打印所有插桩,或仅打印针对指定包的那些。选项:

  • -f:查看它们的关联文件。

list features 命令打印系统的所有功能。

path 命令打印包的 .apk 路径。

install 命令将包安装到系统。选项:

  • -l:使用 FORWARD_LOCK 安装包。
  • -r:重新安装现有应用,保留其数据。
  • -t:允许安装测试 .apk
  • -i:指定安装程序包名称。
  • -s:将包安装到 sdcard。
  • -f:将包安装到内部 flash。

uninstall 命令从系统中移除包。选项:

  • -k:在包移除后保留数据和缓存目录。

clear 命令删除与包关联的所有数据。

enabledisable 命令更改给定包或组件的启用状态。

getInstallLocation 命令获取当前安装位置

  • 0 [auto]:让系统决定最佳位置
  • 1 [internal]:安装到内部设备存储
  • 2 [external]:安装到外部媒体

setInstallLocation 命令更改默认安装位置

列出已安装的包非常简单:

# pm list packages
package:android
package:android.tts
package:com.android.bluetooth
package:com.android.browser
package:com.android.calculator2
package:com.android.calendar
package:com.android.camera
package:com.android.certinstaller
package:com.android.contacts
package:com.android.defcontainer
...

安装应用(这是第 6 章中介绍的 adb install 命令使用的命令):

# pm install FastBirds.apk
  pkg: FastBirds.apk
Success

请注意,移除应用需要知道它的包名,而不是原始 .apk 的名称:

# pm uninstall com.acme.fastbirds
Success

pm 也是一个启动 Java 代码的 shell 脚本。与 am 一样,pm 有比我能在本书中介绍的更多的功能。我鼓励你探索它的许多用途,因为它对脚本非常有用,无论是在开发期间和/或在生产环境中。

svc

与前两个命令不同,svc 在尝试为你提供控制多个系统服务的能力方面是一种瑞士军刀式的工具。以下是 2.3/Gingerbread 中的在线帮助:

# svc
Available commands:
    help     Show information about the subcommands
    power    Control the power manager
    data     Control mobile data connectivity
    wifi     Control the Wi-Fi manager

4.2/Jelly Bean 的在线帮助显示它现在还可以处理 USB:

root@android:/ # svc
Available commands:
    help     Show information about the subcommands
    power    Control the power manager
    data     Control mobile data connectivity
    wifi     Control the Wi-Fi manager
    usb      Control Usb state

请注意,svc 的功能仅限于启用和禁用指定系统服务的行为:

# svc help power
Control the power manager
usage: svc power stayon [true|false|usb|ac]
         Set the 'keep awake while plugged in' setting.
# svc help data
Control mobile data connectivity
usage: svc data [enable|disable]
         Turn mobile data on or off.
       svc data prefer
          Set mobile as the preferred data network
# svc help wifi
Control the Wi-Fi manager
usage: svc wifi [enable|disable]
         Turn Wi-Fi on or off.
       svc wifi prefer
          Set Wi-Fi as the preferred data network

总体而言,你应该知道 svc,但你不太可能经常使用它。和 ampm 一样,svc 也是一个脚本,使用 app_process 启动 Java 代码。

ime

ime 命令让你与输入法系统服务通信以控制系统对可用输入法的使用,它在 2.3/Gingerbread 和 4.2/Jelly Bean 中是一样的:

# ime
usage: ime list [-a] [-s]
       ime enable ID
       ime disable ID
       ime set ID

list 命令打印所有启用的输入法。使用 -a 选项查看所有输入法。使用 -s 选项仅查看每个的单个摘要行。

enable 命令允许给定的输入法 ID 被使用。

disable 命令禁止给定的输入法 ID 被使用。

set 命令切换到给定的输入法 ID。

例如,以下是 2.3/Gingerbread 模拟器上可用的输入法列表:

# ime list
com.android.inputmethod.latin/.LatinIME:
  mId=com.android.inputmethod.latin/.LatinIME mSettingsActivityName=com.android.inputmethod.latin.LatinIMESettings
  mIsDefaultResId=0x7f080001
  Service:
    priority=0 preferredOrder=0 match=0x108000 specificIndex=-1 isDefault=false
    ServiceInfo:
      name=com.android.inputmethod.latin.LatinIME
      packageName=com.android.inputmethod.latin
      labelRes=0x7f0c001f nonLocalizedLabel=null icon=0x0
      enabled=true exported=true processName=com.android.inputmethod.latin
      permission=android.permission.BIND_INPUT_METHOD

和所有其他命令一样,ime 是一个使用 app_process 启动 Java 代码的脚本。和 svc 一样,ime 是一个值得记住的命令,但你不太可能经常使用它。

input

input 连接到窗口管理器系统服务,并向系统注入文本或按键事件。以下是它在 2.3/Gingerbread 上的操作方式:

# input
usage: input [text|keyevent]
       input text <string>
       input keyevent <event_code>

以下是它在 4.2/Jelly Bean 上的操作方式:

root@android:/ # input
usage: input ...
       input text <string>
       input keyevent <key code number or name>
       input [touchscreen|touchpad] tap <x> <y>
       input [touchscreen|touchpad] swipe <x1> <y1> <x2> <y2>
       input trackball press
       input trackball roll <dx> <dy>

input 的功能非常简单。例如,它不知道谁在接收事件,只知道事件被发送到当前具有焦点的任何内容。因此,由你来确保任何需要接收你的输入的内容实际上具有焦点。很明显,当你不面对屏幕而是尝试编写这种行为的脚本时,这是很困难的。然而,在某些情况下,你发送的输入的含义不需要焦点。例如,以下是如何从命令行点击主屏幕按钮:

# input keyevent 3

你可能想知道我是怎么知道 3 是 Home 键的。请查看 2.3/Gingerbread 中的 frameworks/base/core/java/android/view/KeyEvent.javaframeworks/base/native/include/android/keycodes.h,或 4.2/Jelly Bean 中的 frameworks/native/include/android/keycodes.h,以获取 Android 识别的完整键码列表。例如,前者包含这样的代码:

public static final int KEYCODE_HOME            = 3;
/** Key code constant: Back key. */
public static final int KEYCODE_BACK            = 4;
/** Key code constant: Call key. */
public static final int KEYCODE_CALL            = 5;
/** Key code constant: End Call key. */
public static final int KEYCODE_ENDCALL         = 6;
...

像所有其他命令一样,input 是一个依赖 app_process 的脚本。

monkey

还有另一个允许你向 Android 提供输入的工具。它叫做 monkey,在 app 开发者文档中有一个关于它的完整部分,标题为"UI/Application Exerciser Monkey"。正如文档所说,monkey 可用于向你的应用提供随机但可重复的输入。例如,此命令将向浏览器应用发送 50 个伪随机输入:

# monkey -p com.android.browser -v 50

然而,monkey 可以做的远不止这些,正如你在 2.3/Gingerbread 的这个输出中可以看到的那样(4.2/Jelly Bean 的非常相似):

# monkey
usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]
              [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]
              [--ignore-crashes] [--ignore-timeouts]
              [--ignore-security-exceptions]
              ...
              [-s SEED] [-v [-v] ...]
              [--throttle MILLISEC] [--randomize-throttle]
              ...
              COUNT

最有趣的是,你可以为 monkey 提供一个脚本来运行预定义的输入集,而不是提供随机输入。这是一个非常有用的功能,用于开发、测试和现场诊断。不幸的是,关于 monkey 的这个非常强大的功能,几乎没有文档。所以,作为参考,这里有一个示例脚本文件:

# This is a sample test script
# Lines starting with '#' are comments
# This part is the "header"
type= custom
count= 100
speed= 1.0
start data >>
# These are the actual instructions to carry out
LaunchActivity(com.android.contacts,com.android.contacts.TwelveKeyDialer)
UserWait(2500)
DispatchPress(KEYCODE_1)
UserWait(200)
DispatchPress(KEYCODE_8)
...
DispatchPress(KEYCODE_ENDCALL)
UserWait(200)
RunCmd(input keyevent 3)
UserWait(1000)
RunCmd(service call statusbar 1)
UserWait(2000)
RunCmd(service call statusbar 2)

要运行此脚本,请使用以下命令行:

# monkey -f myscript 1

此脚本将启动标准拨号器,拨打 1-800-889-8969,等待 10 秒,挂断,返回主屏幕,然后展开并收起状态栏。请注意,最后一部分使用 RunCmd 指令使脚本直接从命令行运行命令;顺便说一下,这些是我们之前看到的命令。

为了更深入地了解 monkey 理解的脚本语言以及每个命令可以采用的参数,我邀请你查看 monkey 的脚本解释代码 development/cmds/monkey/src/com/android/commands/monkey/MonkeySourceScript.java 并查找 EVENT_KEYWORD_。然后你会找到事件关键字,如 DispatchPressUserWait 和许多其他关键字。

为了实现其功能,monkey 与活动管理器、窗口管理器和包管理器通信。它也是一个 shell 脚本,依赖 app_process 启动实现该实用程序的 Java 代码。

bmgr

自 2.2/Froyo 以来,Android 包含了备份功能,允许用户将数据备份到云端,以便在丢失或更换设备后可以恢复。Google 本身通过充当可能的传输之一来提供此功能的某些部分,但其他传输提供商也可以提供替代传输。在 Android 中提供给应用开发者的 API 是传输无关的。然而,这仍然是一个非常特定于手机和平板电脑使用场景的功能,在嵌入式环境中可能不需要。有一个工具允许你从命令行控制备份管理器系统服务的行为:

# bmgr
usage: bmgr [backup|restore|list|transport|run]
       bmgr backup PACKAGE
       bmgr enable BOOL
       bmgr enabled
       bmgr list transports
       bmgr list sets
       bmgr transport WHICH
       bmgr restore TOKEN
       bmgr restore PACKAGE
       bmgr run
       bmgr wipe PACKAGE

如果这与你的 Android 使用场景相关,请查看 app 开发者手册的"数据备份"部分,以及 Google 关于其自己的备份传输的信息。像我们看到的许多其他命令一样,app_process 用于启动与备份管理器服务交互的实际 Java 代码。

stagefright

Android 的关键特性之一是其丰富的媒体层,AOSP 包含让你与它交互的工具。更具体地说,stagefright 命令与媒体播放器服务交互,允许你进行媒体播放。以下是 2.3/Gingerbread 中的在线帮助(4.2/Jelly Bean 的稍有扩展):

# stagefright -h
usage: stagefright
       -h(elp)
       -a(udio)
       -n repetitions
       -l(ist) components
       -m max-number-of-frames-to-decode in each pass
       -b bug to reproduce
       -p(rofiles) dump decoder profiles supported
       -t(humbnail) extract video thumbnail or album art
       -s(oftware) prefer software codec
       -o playback audio
       -w(rite) filename (write to .mp4 file)
       -k seek test

以下是你如何播放 .mp3 文件的示例:

# stagefright -a -o /sdcard/trainwhistle.mp3

你可能还想研究在 stagefright 源码旁边找到的 recordaudioloop 实用程序。它们几乎没有文档,但所有三个实用程序都是用 C 编写的,与我们迄今为止看到的大多数系统服务专用工具不同——那些主要是用 Java 编写的并通过使用 app_process 的脚本激活。此外,虽然 stagefright 直接与媒体播放器服务通信,但 recordaudioloop 命令使用一个 OMXClient,它方便地包装了与同一服务的通信。

Dalvik 工具

我们已经看到如何使用 am 命令发送 intent,从而触发新的应用启动,每个新应用都带有自己的 Zygote-forked Dalvik 实例。我们还看到了 app_process 命令如何用于使用 Android 运行时启动 Java 编码的命令行工具。然而,在某些情况下,你可能希望放弃所有 Android 特定的层,直接使用 Dalvik。以下是允许你这样做的命令。

dalvikvm

如果你还没有问自己是否有办法在没有任何 Android 特定功能的情况下启动一个纯 Dalvik 虚拟机,这里有你一直在寻找的命令:

# dalvikvm -help
dalvikvm: [options] class [argument ...]
dalvikvm: [options] -jar file.jar [argument ...]
...

dalvikvm 实际上是一个与"Android"完全无关的原始 Dalvik 虚拟机。它不依赖 Zygote,也不包含 Android 运行时。它只是启动一个虚拟机来运行你提供的任何类或 JAR 文件。它在 AOSP 本身中使用得不多,可能是因为 AOSP 中几乎没有不在"Android"上下文中运行的东西。

dvz

启动 Dalvik 虚拟机的另一种方式是 dvz 命令:

# dvz --help
Usage: dvz [--help] [-classpath <classpath>]
[additional zygote args] fully.qualified.java.ClassName [args]

如描述所暗示的,dvz 实际上与活动管理器类似,通过请求 Zygote fork 并启动一个新进程。唯一的区别是,结果进程不受活动管理器管理。相反,它是完全独立的。

目前尚不清楚这个工具是否打算被大量使用,因为在 2.3/Gingerbread 中,它只在测试代码中使用,而在 4.2/Jelly Bean 的默认构建中甚至没有包含。尽管如此,在你可能需要的情况下,在你的工具包中有这个可能会很有用。

启动 Dalvik 的多种方式

到目前为止,我们已经看到了启动 Dalvik 虚拟机的四种不同方式。值得花点时间将它们都放在一起来看。表 7-1 描述了每种获取工作 Dalvik 虚拟机的方式,以及虚拟机中包含什么以及如何启动它。

表 7-1. 启动 Dalvik 的方式

命令 Dalvik 虚拟机 Android 运行时 Zygote 活动管理器 机制
dalvikvm 使用 libdvm.so
app_process 使用 libandroid_runtime.so
dvz 使用 libcutils
am 与活动管理器通信

am 是唯一为我们提供实际由活动管理器控制的 Dalvik 虚拟机实例的命令。在所有其他情况下,虚拟机是独立的,不受生命周期管理。am 也是唯一允许我们自动触发包含在 .apk 中的代码执行的命令。所有其他命令都要求我们提供特定的类或 JAR 文件。

dexdump

如果你想对 Android 应用或 JAR 文件进行反向工程,你可以使用 dexdump

# dexdump
dexdump: [-c] [-d] [-f] [-h] [-i] [-l layout] [-m] [-t tempfile] dexfile...
 -c : verify checksum and exit
 -d : disassemble code sections
 -f : display summary information from file header
 -h : display file header details
 -i : ignore checksum failures
 -l : output layout, either 'plain' or 'xml'
 -m : dump register maps (and nothing else)
 -t : temp file name (defaults to /sdcard/dex-temp-*)