3.4.2 调试的方法

由于Android系统基于Linux,Android中的调试方式首先可以利用标准Linux的基本调试的方法,除此之外,Android中还有特定的调试方法。

1.查看系统进程的情况

首先可以在终端使用ps命令查看系统的进程。启动Android的仿真器环境,然后使用adb shell进行连接,使用ps查看各个进程,如下所示:

    # ps
    USER     PID   PPID  VSIZE  RSS     WCHAN    PC         NAME
    root      1     0     312    220   c009b74c 0000ca4c S /init
    root      2     0     0      0     c004e72c 00000000 S kthreadd
    root      3     2     0      0     c003fdc8 00000000 S ksoftirqd/0
    root      4     2     0      0     c004b2c4 00000000 S events/0
    root      5     2     0      0     c004b2c4 00000000 S khelper
    root      6     2     0      0     c004b2c4 00000000 S suspend
    root      7     2     0      0     c004b2c4 00000000 S kblockd/0
    root      8     2     0      0     c004b2c4 00000000 S cqueue
    root      9     2     0      0     c018179c 00000000 S kseriod
    root      10    2     0      0     c004b2c4 00000000 S kmmcd
    root      11    2     0      0     c006fc74 00000000 S pdflush
    root      12    2     0      0     c006fc74 00000000 S pdflush
    root      13    2     0      0     c00744e4 00000000 S kswapd0
    root      14    2     0      0     c004b2c4 00000000 S aio/0
    root      22    2     0      0     c017ef48 00000000 S mtdblockd
    root      23    2     0      0     c004b2c4 00000000 S kstriped
    root      24    2     0      0     c004b2c4 00000000 S hid_compat
    root      25    2     0      0     c004b2c4 00000000 S rpciod/0
    root      26    2     0      0     c019d16c 00000000 S mmcqd
    root      27    1     740    220   c0158eb0 afd0d8ac S /system/bin/sh
    system    28    1     808    208   c01a94a4 afd0db4c S /system/bin/servicemanager
    root      29    1     3736   372   ffffffff afd0e1bc S /system/bin/vold
    root      30    1     3716   316   ffffffff afd0e1bc S /system/bin/netd
    root      31    1     668    184   c01b52b4 afd0e4dc S /system/bin/debuggerd
    radio     32    1     5392   480   ffffffff afd0e1bc S /system/bin/rild
    root      33    1     81940  22164 c009b74c afd0dc74 S zygote
    media     34    1     19508  1836  ffffffff afd0db4c S /system/bin/mediaserver
    bluetooth 35    1     1260   288   c009b74c afd0e98c S /system/bin/dbus-daemon
    root      36    1     812    236   c02181f4 afd0d8ac S /system/bin/installd
    keystore  37    1     1616   216   c01b52b4 afd0e4dc S /system/bin/keystore
    root      38    1     740    220   c003da38 afd0e7bc S /system/bin/sh
    root      39    1     840    280   c00b8fec afd0e90c S /system/bin/qemud
    root      41    1     3384   176   ffffffff 0000ecc4 S /sbin/adbd
    root      54    38    796    248   c02181f4 afd0d8ac S /system/bin/qemu-props
    system    62    33    152636 30928 ffffffff afd0db4c S system_server
    app_9     121   33    108964 18128 ffffffff afd0eb08 S com.android.inputmethod.latin
    radio     125   33    120308 19396 ffffffff afd0eb08 S com.android.phone
    app_27    128   33    108092 21528 ffffffff afd0eb08 S com.android.launcher
    system    131   33    109420 16932 ffffffff afd0eb08 S com.android.settings
    app_8     157   33    118524 22096 ffffffff afd0eb08 S android.process.acore
    app_0     182   33    116196 17064 ffffffff afd0eb08 S com.android.mms
    app_16    188   33    104244 17520 ffffffff afd0eb08 S android.process.media
    app_6     207   33    106856 18020 ffffffff afd0eb08 S com.android.email
    app_10    218   33    103260 16800 ffffffff afd0eb08 S com.android.bluetooth
    app_12    226   33    10421617020ffffffffafd0eb08Scom.android.providers.calendar
    app_15    240   33    103028 16900 ffffffff afd0eb08 S com.android.deskclock
    app_3     248   33    102408 15832 ffffffff afd0eb08 S com.android.protips
    app_4     255   33    105144 16748 ffffffff afd0eb08 S com.android.quicksearchbox
    app_13    262   33    103452 16164 ffffffff afd0eb08 S com.android.music
    root      268   41    740    328   c003da38 afd0e7bc S /system/bin/sh
    root      282   268   888    332   00000000 afd0d8ac R ps

从Android系统的进程中可以看到,1号和2号进程以0号进程为父进程。init是系统运行的第1个用户空间进程,即Android根目下的init可执行程序,这是一个用户空间的进程。kthreadd是系统的2号进程,这是一个内核进程,其他内核进程都直接或间接以kthreadd为父进程。

系统的各个守护进行由init进行运行,例如Zygote、/system/bin/sh、/system/bin/mediaserver、/system/bin/adbd等,因此它们的父进程号为1,即以init为父进程。

各种文字名称的进程表示Android应用程序的进程,例如android.process.acore、com.android.mms等进程代表的是应用程序进程,它们的父进程都是zygote。例如,在本例中它们的父进程id均为33。

本例中ps命令的进城id为282,其父进程是pid为268的/system/bin/sh,可见这个命令是从sh分支出来的,而pid为268的/system/bin/sh的父进程是pid为41的/system/bin/adbd (adb守护进程)。

使用Linux的proc文件系统,也可以查看进程相关的信息,在/proc目录中,包含了各个进程的信息,每个进程一个目录,就是进程的id。

例如,查看27号进程的目录如下所示:

    # ls -l /proc/27/
    dr-xr-xr-x root     root               2010-08-13 03:39 task
    dr-x------ root     root               2010-08-13 03:39 fd
    dr-x------ root     root               2010-08-13 03:39 fdinfo
    dr-xr-xr-x root     root               2010-08-13 03:39 net
    -r-------- root     root             0 2010-08-13 03:39 environ
    -r-------- root     root             0 2010-08-13 03:39 auxv
    -r--r--r-- root     root             0 2010-08-13 03:39 status
    -r-------- root     root             0 2010-08-13 03:39 personality
    -r-------- root     root             0 2010-08-13 03:39 limits
    -rw-r--r-- root     root             0 2010-08-13 03:39 sched
    -r--r--r-- root     root             0 2010-08-13 03:38 cmdline
    -r--r--r-- root     root             0 2010-08-13 03:34 stat
    -r--r--r-- root     root             0 2010-08-13 03:39 statm
    -r--r--r-- root     root             0 2010-08-13 03:39 maps
    -rw------- root     root             0 2010-08-13 03:39 mem
    lrwxrwxrwx root     root               2010-08-13 03:39 cwd -> /
    lrwxrwxrwx root     root               2010-08-13 03:39 root -> /
    lrwxrwxrwx root     root               2010-08-13 03:39 exe -> /system/bin/sh
    -r--r--r-- root     root             0 2010-08-13 03:39 mounts
    -r--r--r-- root     root             0 2010-08-13 03:39 mountinfo
    -r-------- root     root             0 2010-08-13 03:39 mountstats
    --w------- root     root             0 2010-08-13 03:39 clear_refs
    -r--r--r-- root     root             0 2010-08-13 03:39 smaps
    -r-------- root     root             0 2010-08-13 03:39 pagemap
    -r--r--r-- root     root             0 2010-08-13 03:39 wchan
    -r--r--r-- root     root             0 2010-08-13 03:39 schedstat
    -r--r--r-- root     root             0 2010-08-13 03:39 cgroup
    -r--r--r-- root     root             0 2010-08-13 03:39 oom_score
    -rw-r--r-- root     root             0 2010-08-13 03:39 oom_adj
    -rw-r--r-- root     root             0 2010-08-13 03:39 coredump_filter

在每个进程的目录中,包含了若干个文件的子目录,其中cmdline文件表示了这个进程所在的命令行:

    # cat /proc/27/cmdline
    /system/bin/sh

由此可见,这个27号进程就是shell程序。查看status文件,可以获知这个进程的一些相关信息。对27号进程的查看如下所示:

    # cat /proc/27/status
    Name:     sh                                   # 进程名称
    State:   S (sleeping)                       # 进程状态
    Tgid:     27                                   # 线程组ID
    Pid:      27                                   # 进程ID
    PPid:     1                                    # 父进程ID
    TracerPid:    0
    Uid:0    0    0    0
    Gid:0    0    0    0
    FDSize:  1024
    Groups:
    VmPeak:       744 kB                         # 虚拟内存相关消息
    VmSize:        744 kB
    VmLck:           0 kB
    VmHWM:         316 kB
    VmRSS:         264 kB
    VmData:         92 kB
    VmStk:          84 kB
    VmExe:          80 kB
    VmLib:         460 kB
    VmPTE:          12 kB
    Threads: 1                                    # 所包含的线程数
    SigQ:     0/768
    SigPnd:  0000000000000000
    ShdPnd:  0000000000000000
    SigBlk:  0000000000000000
    SigIgn:  0000000000284004
    SigCgt:  00000000000094ea
    CapInh:  0000000000000000
    CapPrm:  fffffffffffffeff
    CapEff:  fffffffffffffeff
    CapBnd:  fffffffffffffeff
    voluntary_ctxt_switches:   38
    nonvoluntary_ctxt_switches:     1

stat文件实际上包含了比status文件更多的信息,但是可读性比较差,因此一般查看status文件。

进程目录中的fd文件,表示了这个进程中所打开的文件的描述符。对27号进程的查看如下所示:

    # ls /proc/27/fd
    0
    1
    2
    9
    1023

进程目录中的task目录表示了这个进程所包含的子进程,也就是这个进程中所包含的线程,目录的名称也就是子进程的id。对27号进程的查看如下所示:

    # ls /proc/27/task
    27

27号进程只包含一个进程,即27号进程自己。

在Linux操作系统的内核的角度出发,进程和线程没有区别。在用户空间建立的线程(例如,通过pthread建立的)在内核的情况就是一个进程,也就是上述的子进程。事实上,这些子进程的目录结构和进程是一样的。

对于34号进程mediaserver,情况如下所示:

    # cat /proc/34/cmdline
    /system/bin/mediaserver
    # ls
    34
    58
    59
    60
    61
    98

除了34之外,还包含了58,59,60,61,98这4个文件夹,由此可见,mediaserver进程已经包含了若干个子进程。如果查看其status文件,也能发现相应信息。

2.统计系统性能消息

在Linux中有一些可以统计系统性能的方法,在Android中也有所支持。使用vmstat和top命令可以统计系统中性能的信息。

vmstat(Virtual Meomory Statistics,虚拟内存统计),命令报告关于内核线程、虚拟内存、磁盘、陷阱和CPU活动的统计信息。由vmstat命令生成的报告可以用于平衡系统负载活动。

vmstat的使用如下所示:

    # vmstat &
    # procs  memory                        system           cpu
     r  b    free mapped   anon   slab    in   cs  flt  us ni sy id wa ir
     0  0    3792  21112  50864   3724    27   66    0   1  0  1 99  0  0
     0  0    3792  21112  50864   3724    22   53    0   0  0  0 99  0  0
     0  0    3792  21112  50864   3724    28   63    0   1  0  2 98  0  0
     1  0    1752  22168  51348   3744    94  235    4  43  0 18 39  0  0
    10  1    1384  20968  52932   3760   123  555   21  60  0 38  0  2  0
    11  1    1700  19340  53424   3852   366  322    8  80  0 21  0  0  0
     5  0    1996  19748  53688   3800   150  337    5  82  0 17  0  0  0
     4  1    1320  20584  53568   3688   154  584   15  67  0 33  0  0  0
     2  1    1632  20448  53624   3500   150  422   18  66  0 33  0  1  0

vmstat的结构中几个比较重要的值的含义如下所示。

r:在运行队列中等待的进程数

b:在等待io的进程数

w:可以进入运行队列但被替换的进程

free:空闲的内存(单位k)

mapped:影射的内存(单位k)

in:每秒的中断数,包括时钟中断

cs:每秒的环境(上下文)切换次数

us:CPU使用时间

sy:CPU系统使用时间

id:闲置时间

top程序主要可以检测各个进程对CPU的消耗情况,信息将一屏一屏的阶段性地打印到屏幕上。

在Android中,第1次top程序的使用如下所示:

    # top &
    User 93%, System 6%, IOW 0%, IRQ 0%
    User 309 + Nice 0 + Sys 20 + Idle 0 + IOW 0 + IRQ 0 + SIRQ 0 = 329
      PID CPU% S  #THR     VSS     RSS PCY UID      Name
      270  82% S    17 133980K  21628K  fg app_5    com.cooliris.media
      62  12% S    44 152976K  25928K  fg system   system_server
      301   3% R     1    908K    388K  fg root     top
        4   0% S     1      0K      0K  fg root     events/0
        5   0% S     1      0K      0K  fg root     khelper
        6   0% S     1      0K      0K  fg root     suspend
        7   0% S     1      0K      0K  fg root     kblockd/0
    8   0% S     1      0K      0K  fg root     cqueue
    9   0% S     1      0K      0K  fg root     kseriod
   10   0% S     1      0K      0K  fg root     kmmcd
   11   0% S     1      0K      0K  fg root     pdflush
   12   0% S     1      0K      0K  fg root     pdflush
   13   0% S     1      0K      0K  fg root     kswapd0
   14   0% S     1      0K      0K  fg root     aio/0

top使用的第2次统计的内容如下所示:

    User 14%, System 5%, IOW 0%, IRQ 0%
    User 47 + Nice 0 + Sys 18 + Idle 249 + IOW 0 + IRQ 0 + SIRQ 0 = 314
      PID CPU% S  #THR     VSS     RSS PCY UID      Name
      34  13% S    14  29084K   2928K  fg media    /system/bin/mediaserver
      62   3% S    44 153040K  26532K  fg system   system_server
      301   3% R     1    916K    404K  fg root     top
      125   0% S    17 119236K  16508K  fg radio    com.android.phone
      39   0% S     1    844K    336K  fg root     /system/bin/qemud
        6   0% S     1      0K      0K  fg root     suspend
        7   0% S     1      0K      0K  fg root     kblockd/0
        8   0% S     1      0K      0K  fg root     cqueue
        9   0% S     1      0K      0K  fg root     kseriod
      10   0% S     1      0K      0K  fg root     kmmcd
      11   0% S     1      0K      0K  fg root     pdflush
      12   0% S     1      0K      0K  fg root     pdflush
      13   0% S     1      0K      0K  fg root     kswapd0
      14   0% S     1      0K      0K  fg root     aio/0

以上top程序统计的情况是,刚刚打开Gallery(3D版本)程序,因此com.cooliris. media,也就是Gallery3D的程序,这个进程占用的CPU消耗最大,同时top程序本身也占用了一些CPU消耗。然后打开了视频播放程序,因此,/system/bin/mediaserver进程的消耗变为最大。

Andoir中还提供了dumpstat和dumpsys两个工具,可以用于将各个进程的stat中的信息和sys文件系统的内容导出。这两个工具导出的内容都较多,最好在主机端配合adb来使用,例如:

    $ adb shell dumpstat |  grep CPU

3.内核和驱动的调试

使用dmesg可以查看内核打印出来信息,如下所示:

    # dmesg

ioctl是Android中的一个工具,用户可以直接控制设备节点的ioctl命令,这样可以在没有写程序的情况下进行一些测试工作。

ioctl的使用如下所示:

    # ioctl -h
    ioctl [-l <length>] [-a <argsize>] [-rdh] <device> <ioctlnr>
      -l <lenght>   Length of io buffer
      -a <argsize>  Size of each argument (1-8)
      -r             Open device in read only mode
      -d             Direct argument (no iobuffer)
      -h             Print help

例如,使用ioctl程序,查看framebuffer驱动的情况如下所示:

    # ioctl -l 16 -r /dev/graphics/fb0 0x4600
    sending ioctl 0x4600 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00
    return buf: 40 01 00 00 e0 01 00 00 40 01 00 00 c0 03 00 00

linux/fb.h中定义了获取framebuffer信息的ioctl命令FBIOGET_VSCREENINFO为0x4600,如下所示:

    #define FBIOGET_VSCREENINFO     0x4600

FBIOGET_VSCREENINFO这个ioctl命令使用的结构体为struct fb_var_screeninfo,前面几个字节的内容如下所示

    struct fb_var_screeninfo {
        __u32 xres;                   /* 可见分辨率   */
        __u32 yres;
        __u32 xres_virtual;         /* 虚拟分辨率   */
        __u32 yres_virtual;
        /* ......省略部分内容 */
    }

以上的命令使用读取了16字节的消息,得到的信息的含义为:

    0x140==320 0x1E0==480 0x3C0==960

因此,表示屏幕的可见的宽为320,可见的高为480,虚拟的宽为320,虚拟的高为960,这就是双缓冲的屏幕。

4.Android的特殊调试命令

在Android的toolbox中包含了一些非标准化的辅助命令,这些命令在Linux的shell中没有,专门为Android系统所用。

netcfg是Android中的一个网络工具,用于配置网络,使用方法如下所示

    # netcfg -h
    usage: netcfg [<interface> {dhcp|up|down}]

在仿真器连接网络的情况上查看:

    # netcfg
    lo       UP    127.0.0.1       255.0.0.0       0x00000049
    eth0     UP    10.0.2.15       255.255.255.0   0x00001043
    tunl0    DOWN  0.0.0.0         0.0.0.0         0x00000080
    gre0     DOWN  0.0.0.0         0.0.0.0         0x00000080

Service工具用于和Android一个已经启动的Service进行通信。Service命令的使用方式如下所示:

    # service -h
    Usage: service [-h|-?]
          service list
          service check SERVICE
          service call SERVICE CODE [i32 INT | s16 STR] ...
    Options:
      i32: Write the integer INT into the send parcel.
      s16: Write the UTF-16 string STR into the send parcel.

使用service list可以显示系统已经启动的服务,如下所示:

    # service list
    Found 49 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]
    45   media.audio_policy: [android.media.IAudioPolicyService]
    46   media.camera: [android.hardware.ICameraService]
    47   media.player: [android.media.IMediaPlayerService]
    48   media.audio_flinger: [android.media.IAudioFlinger]

这里显示的服务包括了Java层启动的服务和C中启动的服务。例如以上列出的内容中:phone就是Java层启动的服务,media.player中就是C中启动的服务。

使用service check查看服务的状态,如下所示:

    # service check media.player
    Service media.player: found

使用service call调用进程的情况如下所示:

    # service call media.player 3 s16 "file:///sdcard/a.mp4"
    Result: Parcel(
      0x00000000: 00000000 40806d54 a821a0e5 73622a85 '....Tm.@..!..*bs'
      0x00000010: 0000017f 00000000 00000000           '............    ')
    # D/MediaPlayerService(   34): Can't decode    by path, use filedescriptor instead

事实上,这种调用已经调用到了MediaPlayerService中的函数,因此打印出了上述的调试信息。

am命令是在控制台中可以非常容易调试程序的工具,例如可以启动活动、启动服务和发送广播等功能。am命令的基本使用方法如下所示:

    # 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

使用am start是其中的一个功能,以INTENT作为参数,INTENT使用的选项如下所示:

    <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]
        [--activity-clear-when-task-reset] [--activity-exclude-from-recents]
        [--activity-launched-from-history] [--activity-multiple-task]
        [--activity-no-animation] [--activity-no-history]
        [--activity-no-user-action] [--activity-previous-is-top]
        [--activity-reorder-to-front] [--activity-reset-task-if-needed]
        [--activity-single-top]
        [--receiver-registered-only] [--receiver-replace-pending]
        [<URI>]

使用am命令启动计算器应用(实际是使用显式Intent)的方式如下所示:

    # am start -n com.android.calculator2/com.android.calculator2.Calculator

使用am命显示一幅图片(实际是使用隐式Intent)的方式如下所示:

    # am start -a android.intent.action.VIEW -d file:///sdcard/a.jpg -t image/*

5.Logcat工具

logcat是Android中的一个命令行工具,可以用于得到程序的log信息。Logcat的使用方法如下所示:

    logcat [options] [filterspecs]

logcat工具的具体选项如表3-2所示。

表3-2 logcat工具的选项

在Android的本地代码中,通常具有如下的设置。

    //#define LOG_NDEBUG 0
    #define LOG_TAG "XXX"
    #include <utils/Log.h>

将#define LOG_NDEBUG 0的注释取消,即可获得DEBUG的调试信息,而LOG_TAG表示调试信息的前缀。