这两天隔壁部门的同事反馈说新项目上, 车机(Android系统)上挂载的USB外设(一个可以上网的TBOX设备)无法通过ADB(Android Debug Bridge)进行连接. 心里有点纳闷, USB不都识别到了吗, 上次也把ADB相关的客户端都移植过去了, 为啥还会识别不到设备了? 只得从头开始理下ADB相关的代码与逻辑.先来看看ADB的基本原理.
ADB(全称Android Debug Bridge)是Android上用途十分广泛的调试工具, 可用于与开发设备进行连接;ADB命令既可以用来主机与设备之间传输文件, 也可以通过SHELL命令对设备进行操作. 如下所示, 是ADB的一个工作原理图:
可以看到, ADB主要有3个部分组成:
- ADB clients: 客户端程序, 运行在开发设备上, 用于发送指令, 用户可以通过adb shell/devices来激活客户端
- ADBD: 守护进程, 作为后台进程运行在调试设备上, 一般在系统启动的时候加载
- ADB server: 服务端进程, 运行在开发设备上, 负责ADBD与客户端进程的通讯, 由ADB客户端负责加载
对于模拟设备, ADB server与ADB一般是通过TCP/IP协议建立的通讯链接; 对于外接的USB调试设备, 则可以选择通过USB/TCP两种方式进行连接. 一般来说, 一个ADB指令的执行需要经过如下几个步骤:
- 用户输入
adb shell/devices
时, 会判断是否有ADB server
进程存在,如果没有就启动一个进程 - ADB server通过TCP 5037端口监听来自客户进程请求, 在初始化的时候, 如果使用的是TCP连接模式, ADB Server会通过扫面一个固定的TCP端口范围
5555 ~ 5585
中的奇数号来尝试连接到目标调试设备;如果是USB连接模式, 则会通过扫描当前已连接的USB设备, 从中找出用于ADB通讯的接口用于通讯. 不指定连接模式, 则会扫描所有的设备 - ADB server找到设备并建立连接后, 就可以把来自客户端的请求发送给调试设备的ADBD进程了, 由ADBD来执行用户的请求并返回结果
这里就从源码的角度具体分析下ADB的具体执行流程(主要以USB连接模式为例). 在分析具体的流程之前, 不妨来简单看看Android ADB的代码结构. 以Android Pie代码为例:
- ADB的源码位于
/system/core/adb
, 客户端代码位于client
目录, 而ADBD的代码则位于daemon
目录 - 根目录下的代码包含了客户端/服务端以及守护进程的共用的逻辑; 查看共用代码部分时, 定义了
ADB_HOST
宏的表示是需要在HOST端执行的代码逻辑(包括客户端以及服务端), 其他则是调试设备上ADBD的逻辑.
ADB客户端/服务进程的启动
查看client
下面的代码, 有个main函数, 就是输入adb shell//devices
执行的入口:
1 |
|
adb_commandline
最终会调用函数adb_connect
尝试连接服务进程, 如果当前没有服务进程,则启动一个新的进程, 然后读取服务端返回的结果, 直接看下adb_connect
的逻辑:
- 首先尝试连接服务端进程, 没有, 则通过
launch_server
启动一个新的进程 - 启动完成后, 通过
_adb_connect
连接到服务进程
1 |
|
查看launch_server
linux部分的实现, 其实际的入口与客户端的函数入口是一样的, 只是参数不一样而已: 服务端启动完成之后, 发送OK
的状态给客户端, 告知其启动完成.
1 |
|
设备的识别与扫描
我们重点来看下ADB SERVER是如何扫描设备的. 执行服务端进程后, 调用adb_server_main
函数来执行服务端的逻辑:
usb_init
扫描USB设备adb_wait_for_device_initialization
等待扫描完成后, 发送OK
给客户端进程fdevent_loop
进入循环, 等待来自客户端的请求
1 |
|
这里着重看下USB设备的扫描usb_init
<usb_dispatch.cpp
>, 对ADB客户端/服务端都不是使用libusb
来识别USB设备, 直接看linux
下的实现usb_linux.cpp
:
1 |
|
usb_init
主要是启动一个设备查找的线程, 用于不断识别接入的USB设备而已. 到这里, 我们大概知道了如何查找到USB设备并且读取设备的配置: 通过遍历/dev/bus/usb
下面的节点, 即对应USB设备的字符设备, USB设备的信息就可以通过这些字符设备读取到.
1 |
|
对于ADB设备节点来说, 其有特殊的类别以及协议, 因此需要通过is_adb_interface
来过滤掉非ADB的设备.感兴趣的可以仔细看下find_usb_device
这个函数看下具体的设备查找流程.
1 |
|
无法识别ADB设备的问题
梳理了ADB的一些代码, 我们大致知道对于客户端来说,在USB模式下, 只是通过一个特定的USB设备节点来与调试设备通讯, 那么, 客户端找不到设备的原因就只有两个(如前所述, HOST端已经识别到了调试设备):
- 客户端的配置有问题
- 调试设备的ADB配置有问题
可问题在于, 同样的设备在PC上是可以正常识别到并使用ADB的, 那么可以基本可以确信调试设备上的ADB配置是没有问题的;关于如何在调试设备上适配ADB, 大家可以参考之前的一篇文章Recovery下如何配置ADB. 那么, 问题就可以聚焦在客户端的配置上了.
对比下正常的配置与异常的配置, 发现异常的配置tty设备枚举到了5个(从ttyUSB0
到ttyUSB4
), 而正常只有4个(ttyUSB0
到ttyUSB3
), 进入/sys/bus/usb/devices
找到对应的ttyUSB4
设备, 看了下相应的配置,bInterfaceClass
/bInterfaceProtocol
/bInterfaceSubClass
与上述ADB的配置是完全一致的, interface
的结果为ADB interface
. 现在应该可以肯定, 这个多出来的ttyUSB4
串口设备应该就是用于ADB通讯的设备, 但是不知道为何被枚举成了串口. 看看串口驱动源码(drivers/usb/serial/option.c
):
1 |
|
这里明确指出了, 有些保留(RSVD)
的端口不需要枚举, 从这里我们大致可以推测问题的原因: ADB对应的设备节点被枚举成了串口, 导致ADB服务端无法与调试设备进行连接通讯. 就是说, 只要把对应端口号的设备保留下来即可. 找到调试设备VID/PID所在的地方, 加上RSVD
标志即可:
1 |
|
修改后, 就可以看到adb devices
有设备列出了.