adb介绍


请尊重原创版权,转载注明出处。

1. 概要

Android Debug Bridge(adb)是一个Android的命令行工具,可以用来连接模拟器或实际的移动设备。做过Android开发的朋友一定对adb logcat, adb shell命令不陌生,Dalvik Debug Monitor Server(DDMS)后台也是运行的adb来实现监控调试移动设备。

总体而言,adb有两个用途:

  • 监控连接设备 adb会监控所有已经连接设备(包括模拟器),譬如设备所处的状态:ONLINE,OFFLINE, BOOTLOADER或RECOVERY。

  • 提供操作命令 adb提供了很多命令(adb shell, adb pull),来实现对设备的操控。这些操作命令在adb的体系里面,都称为“服务”。


2. 工作原理

adb的工作原理可以用下图来表示:

adb命令执行会涉及到三个组成部分(client, server,daemon)和两个数据通道,下面我们将深入对adb的工作原理进行介绍。

2.1 adb的三个组成元素

  • adb client 在本地客户端的可执行程序,下载Android SDK便可获取这个执行程序,通过在命令行执行adb,就启动了adb的本地客户端程序。我们常用的命令,adb devices, adb shell, adb logcat,都是先交由本地客户端程序处理的。

  • adb server 本地客户端并不能独立完成工作,当我们输入adb命令时,客户端会尝试连接本地的服务端程序。如果服务端程序没有启动,则启动一个本地的服务端程序。为什么客户端输入adb命令能够自动启动服务端呢?因为客户端和服务端实际上是集成在一个可执行程序里面的,在Linux系统上,是adb;在Windows系统上,是adb.exe

  • adb daemon(adbd) 在模拟器或移动设备上运行的后台服务。当Android系统起机的时候,由init程序启动adbd。如果adbd挂了,则adbd会由init重新启动。换言之,只要Android系统在运行,那adbd就是“不死的”,常年在伺服状态。

client和server虽然是同一个执行程序,但在命令行输入一条adb命令后,实际上完成了一次通信。在server启动的时候,会将自己绑定到本地的5037端口,当client有请求到来时,便通过TCP连接server的5037端口。

通过以下命令,可以看到server的启动日志:

$ adb kill-server && adb devices
* daemon not running. starting it now on port 5037 *
* daemon started successfully *

通过以下命令,可以看到TCP的5037端口,在侦听连接:

$ netstat -l | grep 5037
Proto Recv-Q Send-Q    Local Address   Foreign Address     State
tcp        0      0    127.0.0.1:5037  0.0.0.0:*           LISTEN

当我们执行一些常用的adb命令时,譬如adb devices,adb shell, server就自动启动了,也可以通过adb start-server来启动;如果想要停止server的运行,可以通过adb kill-server来杀掉server进程。

server只有一个,但client是可以有多个的。打开两个命令行,都输入adb shell命令,然后,再在第三个命令行输入以下命令,查看输出结果:

$ lsof -i | grep adb
COMMAND   PID   TYPE DEVICE NODE NAME
adb     23613   IPv4 402225 TCP  localhost:38491->localhost:5037 (ESTABLISHED)
adb     23710   IPv4 407399 TCP  localhost:38494->localhost:5037 (ESTABLISHED)

可以看到,建立了两个TCP连接,端口号的对应关系分别是:38491到5037, 38494到5037。这说明,多个clients可以同时和server建立连接。

2.2 adb的两个数据通道

2.2.1 client与server的数据通道

这个数据通道是一个本地TCP连接,adb server启动以后,在本地的5037端口侦听。adb client通过本地的随机端口与5037端口建立连接。

在这个通道上,client向server发送的命令都遵循如下格式:

  1. 命令的长度(Length),由四位的十六进制表示
  2. 实际的命令(Payload),通过ASCII编码

譬如,查看adb当前的版本,client会发起如下命令:

000Chost:version

000C表示”host:version”这条命令的长度为12个字节。命令中使用了host前缀,目的是为了区分其他类型的命令(后面还会看到shell前缀的命令),host前缀的命令可以理解为只在client与server这个数据通道上存在。

server收到client的请求后,返回的数据遵循如下格式:

  1. 如果成功,则返回四个字节的字符串”OKAY”
  2. 如果失败,则返回四个字节的字符串”FAIL”和出错原因
  3. 如果异常,则返回错误码

当Client收到Server返回的”OKAY”后,就可以发继续发起其他操作命令了。

2.2.2 server与adbd的数据通道

这个数据通道对client而言,完全是透明的,client不关注这个通道怎么建立以及怎么进行数据传输。

当连接模拟器时,这个数据通道也是一个本地的TCP连接;当连接实际的设备时,这个数据通道通常是USB数据线的连接,当前,adb也支持远程的TCP连接。

adb server启动后,会在5037端口侦听从client发起的TCP连接,同时,也会试图与5555~5585这些端口建立TCP连接。当Android模拟器启动或者手机连接上PC时,就会用到5555~5585这些端口,简单理解就是adbd在PC上占用的端口号。每一个adbd都会占用两个端口,一个偶数号端口,用于命令行的连接;一个奇数号端口,用于adb的连接。

client与adbd的数据传输是需要用到两个通道的,当与server建立第一个通道的连接后,需要向server发送transport命令,表示接下来,要与adbd进行数据传输。transport命令有以下一些形式:

  • host:transport:<serial-number> 用于向指定的设备发送transport命令
  • host:transport-usb 用于向USB连接的设备发送transport命令,当有多个设备通过USB连接时,server会返回“FAIL”
  • host:transport-local 用于向模拟器发送transport命令,当有多个模拟器启动时,server会返回“FAIL”
  • host:transport-any 用于向任何连接的设备或模拟器发送transport命令,有多个连接时,server会返回“FAIL”

当server返回“OKAY”后,client后续发送的数据,就直接传输到adbd了。譬如shell:command arg1 arg2 ...命令,表示client往adbd发送的shell命令。

下图是在客户端输入adb shell ls命令后,adb的一个工作时序:


3. 其他

3.1 源码目录

adb的源码在system/core/adb目录下,adb和adbd两个二进制程序都是从这个目录下的代码中编译出来的,通过宏编译开关ADB_HOST来控制:

#if ADB_HOST
/* code for adb */
#else
/* code for adbd */
#endif

3.2 USB Vendor ID

当通过USB线连接手机时,adb需要检查手机厂商的ID,即Vendor ID。 adb已经内置了很多Vendor ID,但仍然不能涵盖所有的手机厂商。

当出现adb无法找到设备时,我们需要手工添加Vendor ID,如何知道手机厂商的Vendor ID是多少呢? 通过lsusb命令可以查看:

duanqizhi@xo:~$ lsusb
Bus 002 Device 005: ID 413c:2107 Dell Computer Corp.
Bus 002 Device 004: ID 046d:c077 Logitech, Inc.
Bus 002 Device 003: ID 0424:2514 Standard Microsystems Corp. USB 2.0 Hub
Bus 002 Device 002: ID 8087:8000 Intel Corp.
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 021: ID 2a45:0003
Bus 001 Device 002: ID 8087:8008 Intel Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

无法识别设备的Vendor ID就是2a45,在$HOME/.android/adb_usb.ini文件末尾,将添加一行2a45即可。

3.3 几条有趣的adb命令

$ adb hell
$ adb lolcat