第24章 USB调试:USB Gadget、MTP/PTP、ADB调试、充电问题
USB调试,这大概是Android工程师打交道最多的模块之一了。说实话,USB这块看似简单,但坑是真不少。我见过不少同事,明明代码逻辑没问题,就是连不上adb,或者MTP传输到一半就断了。今天咱们就把USB Gadget、MTP/PTP、ADB调试和充电问题这几个核心点彻底捋一遍。
24.1 USB Gadget:Android的“变形金刚”
先说说USB Gadget。这个概念可能有些朋友不太熟,但它的作用非常关键。说白了,USB Gadget就是让Android设备能“扮演”不同的USB角色。
比如你插上电脑,它可以是MTP设备(传文件),可以是ADB设备(调试),也可以是充电设备。这些角色切换,全靠Gadget框架在底层调度。
我习惯把Gadget理解成一个“多面手”。它基于Linux的USB Gadget框架,通过配置文件来决定当前设备“扮演”什么。核心文件在 /sys/kernel/config/usb_gadget/ 下面。
核心思路: 一个USB物理端口,通过Gadget配置,可以同时支持多个功能(比如ADB + MTP)。这叫“复合设备”(Composite Device)。
来看一个典型的配置流程:
# 创建一个gadget实例
mkdir /sys/kernel/config/usb_gadget/g1
cd /sys/kernel/config/usb_gadget/g1
# 设置VID/PID(厂商ID和产品ID)
echo 0x18d1 > idVendor
echo 0x4ee7 > idProduct
# 创建字符串描述
mkdir strings/0x409
echo "Google" > strings/0x409/manufacturer
echo "Pixel 6" > strings/0x409/product
echo "0123456789ABCDEF" > strings/0x409/serialnumber
# 创建配置
mkdir configs/c.1
mkdir configs/c.1/strings/0x409
echo "Config 1: ADB + MTP" > configs/c.1/strings/0x409/configuration
# 添加ADB功能
mkdir functions/ffs.adb
ln -s functions/ffs.adb configs/c.1/
# 添加MTP功能
mkdir functions/mtp.gs0
ln -s functions/mtp.gs0 configs/c.1/
# 绑定UDC(USB Device Controller)
echo "musb-hdrc.0.auto" > UDC
嗯,这里要注意:UDC的名字每个平台不一样。我遇到过好几次,明明配置都对了,就是绑定失败,最后发现是UDC名字写错了。可以用 ls /sys/class/udc/ 查看当前可用的UDC。
24.2 MTP/PTP:文件传输的“面子”和“里子”
MTP(Media Transfer Protocol)和PTP(Picture Transfer Protocol)是用户最直观感受到的USB功能。插上电脑,能看到手机里的照片、视频,靠的就是它们。
但说实话,MTP在Android上的实现一直有点“脆弱”。我曾在项目中遇到一个经典问题:MTP连接后,电脑上能看到设备,但点进去是空的。折腾了半天,发现是 MTP的存储路径配置错了。
MTP的核心配置在 /system/etc/mtp/mtp.conf 或 /vendor/etc/mtp/mtp.conf 里。它决定了哪些目录暴露给电脑。
# 典型的mtp.conf内容
# 每一行代表一个暴露的存储路径
/storage/emulated/0
/storage/XXXX-XXXX
如果路径不存在,或者权限不对,MTP就会“罢工”。我建议你在调试MTP问题时,先确认这几个点:
- 存储路径是否存在:用
adb shell ls -l /storage/检查 - SELinux上下文是否正确:MTP需要
mtp_device或fuse上下文 - FUSE文件系统是否挂载:Android 11+ 默认用FUSE模拟sdcard,MTP依赖它
小技巧: 如果MTP连上后传输大文件经常中断,可以试试关闭USB的“仅充电”模式,或者换一根质量好点的数据线。别笑,我遇到过好几次,最后发现是线的问题。
24.3 ADB调试:开发者的“生命线”
ADB调试,这个不用我多说了吧?每个Android工程师的必备技能。但ADB连不上、掉线、授权失败这些问题,你肯定也遇到过。
ADB的底层依赖USB Gadget的 FunctionFS 功能。它通过 /dev/usb-ffs/adb/ 这个文件系统和上层通信。
我总结了一个ADB调试的“三板斧”:
- 检查USB连接状态:
lsusb看设备是否被识别 - 检查ADB服务状态:
adb devices看是否列出设备 - 检查授权状态:
adb kill-server; adb start-server; adb devices重新授权
如果还是连不上,那就得看内核日志了:
# 抓取USB相关日志
adb shell dmesg | grep -i usb
adb logcat -b all | grep -i adb
我曾经遇到一个奇葩问题:ADB能连上,但执行 adb shell 就卡死。查了半天,发现是 adbd 进程的权限被SELinux限制了。解决方案是在 adbd.te 里加上对应的allow规则。
警告: 不要在生产环境的user版本上开启ADB调试。我见过有人把eng版本的adbd直接推到user版本上,结果导致安全漏洞。ADB调试只应该在eng或userdebug版本上使用。
24.4 充电问题:不只是“充不进去”
充电问题,表面看是硬件的事,但很多时候是软件配置的锅。USB充电涉及 BC1.2协议(Battery Charging Specification),它决定了设备能从USB口获取多少电流。
Android的充电逻辑在 healthd 或 charger 服务里。核心是读取 /sys/class/power_supply/ 下的节点。
我习惯用这几个命令快速诊断充电问题:
# 查看电池状态
cat /sys/class/power_supply/battery/status
# 查看当前电流
cat /sys/class/power_supply/battery/current_now
# 查看USB类型
cat /sys/class/power_supply/usb/type
常见的充电问题有:
- 充电慢:可能是USB类型识别为SDP(标准下行端口),电流只有500mA。检查D+/D-线是否短路
- 不充电:可能是BC1.2协商失败,或者充电IC的寄存器配置不对
- 充电断断续续:可能是接触不良,或者温控策略太激进
避坑指南: 我曾经在调试一款平板时,发现插上充电器后设备显示“充电中”,但电量一直掉。最后发现是 充电IC的输入电流限制 设得太低,只有100mA。设备耗电比充电快,当然充不进去。修改 dts 里的 input_current_limit 后解决。
24.5 知识体系总览
下面这张图,把USB调试的核心模块和它们之间的关系画出来了。你可以把它当作一个快速索引。
这张图里,USB Gadget是“大脑”,它决定了设备能提供哪些功能。MTP、ADB、充电是三个主要“输出”。每个输出背后,都有对应的配置文件和内核驱动在支撑。
调试的时候,我习惯从Gadget层开始查。先确认Gadget是否注册成功,再看具体功能是否使能。这样一层层往下,问题通常都能定位到。
好了,USB调试这块内容不少,但核心就是这几个点。你只要把Gadget的配置逻辑搞明白,MTP的路径和权限搞清楚,ADB的授权和SELinux弄明白,充电的协议和节点摸清楚,大部分问题都能迎刃而解。