尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

第 39 章:USB、ADB 与 MTP

第 39 章:USB、ADB 与 MTP Android 的 USB 连接可同时服务三类完全不同的使用场景:开发者通过 ADB 调试应用、普通用户通过 MTP 传输照片、配件厂商通过 USB 主机模式接入游戏手柄。每一类场景都使用软件栈中独立的模块,该软件栈覆盖从用户空间 Java 服务,一直到 Linux 内核的 USB 小工具(gadget)与主机控制器驱动。本章追踪 USB 线路上每一字节数据的流向,依次经过 HAL 层、框架层服务,最终抵达应用层,全文会引用真实的 AOSP 源码路径。39.1 USB 框架概述39.1.1 整体架构概览Android USB 子系统划分为 4 个垂直层级:公开 SDK API(UsbManager)、系统服务(UsbService 及其子管理器)、硬件抽象层(AIDL HAL 接口 IUsb、IUsbGadget)、Linux 内核 USB 子系统(gadget 驱动、主机控制器驱动、configfs、functionfs)。39.1.2 核心组件组件类型源码路径作用UsbManagerSDK APIframeworks/base/core/java/android/hardware/usb/UsbManager.java应用使用的公开 APIUsbService系统服务frameworks/base/services/usb/.../UsbService.java核心协调器UsbDeviceManager内部管理器frameworks/base/services/usb/.../UsbDeviceManager.javaGadget 模式状态机UsbHostManager内部管理器frameworks/base/services/usb/.../UsbHostManager.java主机模式设备枚举UsbPortManager内部管理器frameworks/base/services/usb/.../UsbPortManager.javaType‑C 端口管理UsbPermissionManager内部管理器frameworks/base/services/usb/.../UsbPermissionManager.java按用户跟踪权限IUsbAIDL HALhardware/interfaces/usb/aidl/.../IUsb.aidl端口状态、角色切换IUsbGadgetAIDL HALhardware/interfaces/usb/gadget/aidl/.../IUsbGadget.aidlGadget 功能配置adbd原生守护进程packages/modules/adb/daemon/main.cppADB 守护进程MTP Native原生库frameworks/av/media/mtp/MTP 协议实现MTP ServiceJava 服务packages/services/Mtp/MTP 文档提供者39.1.3 双模式架构:Gadget 模式与主机模式同一个 USB Type‑C 端口可工作在两种完全不同的模式,模式由 USB 供电协议协商得到的数据角色决定:设备 / Gadget 模式(UFP):Android 设备作为外设接入主机(通常是 PC)。支持 MTP 文件传输、ADB 调试、PTP 照片传输、RNDIS 网络共享、MIDI、USB 配件 AOA。内核 USB gadget 框架(configfs)对外提供复合 USB 功能。主机模式(DFP):Android 设备充当 USB 主机。会枚举接入的 USB 外设(键盘、鼠标、存储、音频设备),并通过 UsbManager API 向应用提供访问。UsbPortManager 通过 IUsb HAL 监听端口状态变更,协调两种模式之间的切换。39.1.4 UsbManager —— 公开 APIUsbManager(源码:frameworks/base/core/java/android/hardware/usb/UsbManager.java)带有@SystemService注解,是应用与 USB 交互的入口。它提供:设备 (gadget) 模式操作:查询与设置当前 USB 功能(MTP、PTP 等)获取 USB 配件信息打开 USB 配件连接主机模式操作:枚举已连接 USB 设备(getDeviceList())请求设备通信权限打开设备连接(openDevice())功能常量定义可用的 gadget 配置:// 摘自UsbManager.java —— 功能位掩码 public static final long FUNCTION_NONE = 0; public static final long FUNCTION_MTP = GadgetFunction.MTP; // 1 2 public static final long FUNCTION_PTP = GadgetFunction.PTP; // 1 4 public static final long FUNCTION_RNDIS = GadgetFunction.RNDIS; // 1 5 public static final long FUNCTION_MIDI = GadgetFunction.MIDI; // 1 3 public static final long FUNCTION_ACCESSORY = GadgetFunction.ACCESSORY; // 1 1 public static final long FUNCTION_AUDIO_SOURCE = GadgetFunction.AUDIO_SOURCE; // 1 6 public static final long FUNCTION_ADB = GadgetFunction.ADB; // 1 public static final long FUNCTION_NCM = GadgetFunction.NCM; // 1 10 public static final long FUNCTION_UVC = GadgetFunction.UVC; // 1 7这些常量直接映射 AIDL 可序列化对象 GadgetFunction,定义位置:hardware/interfaces/usb/gadget/aidl/android/hardware/usb/gadget/GadgetFunction.aidl。39.1.5 UsbService —— 核心协调器UsbService(源码:frameworks/base/services/usb/java/com/android/server/usb/UsbService.java)实现 IUsbManager,运行在 system_server 进程。它是所有 USB 操作的 Binder 服务端点,将工作委派给各个专用子管理器:UsbAuthManager 仅在enableUsbHostAuthorization标记开启时才会实例化(参考 UsbService.java 中mAuthManager = new UsbAuthManager(...)附近代码);它对接独立进程 Rust 守护进程,详见 39.10 节。服务生命周期遵循标准 SystemService 模式:构造阶段:system_server 启动时实例化 UsbService。systemReady():触发所有子管理器初始化。UsbHostManager 启动原生线程监听/dev/bus/usb设备插拔事件。UsbPortManager 向 HAL 查询当前端口状态。运行阶段:处理来自应用的 Binder 调用,广播 USB 状态变更,按用户配置管理权限与设置。39.1.6 系统属性与 Sysfs 路径UsbDeviceManager 通过多个内核接口与系统属性监控、控制 USB 状态:接口路径用途USB 状态 sysfs/sys/class/android_usb/android0/state旧版 gadget 状态USB 功能 sysfs/sys/class/android_usb/android0/functions旧版功能配置UDC 控制器sys.usb.controller(系统属性)ConfigFS UDC 名称USB 配置persist.sys.usb.config(系统属性)持久化 USB 配置RNDIS 地址/sys/class/android_usb/android0/f_rndis/ethaddr网络共享 MAC 地址MIDI ALSA/sys/class/android_usb/android0/f_midi/alsaMIDI 设备信息UEvent 匹配DEVPATH=/devices/virtual/android_usb/android0旧版状态变更UEvent 匹配SUBSYSTEM=udc现代 UDC 状态变更FunctionFS/dev/usb‑ffs/adb/ADB FunctionFS 端点39.2 UsbDeviceManager:Gadget 模式状态机39.2.1 概述UsbDeviceManager(源码:frameworks/base/services/usb/java/com/android/server/usb/UsbDeviceManager.java)是 USB 框架中最复杂的组件。它管理 Android 设备作为 USB 外设的行为,处理功能切换(MTP、PTP、RNDIS、配件、MIDI、ADB)、线缆事件触发的状态迁移,同时协调锁屏状态、用户偏好、内核层 USB 配置。该类实现ActivityTaskManagerInternal.ScreenObserver,用于响应锁屏状态变更。关键点:MTP 访问用户存储数据要求设备屏幕处于解锁状态。39.2.2 架构39.2.3 双 Handler 策略UsbDeviceManager 在构造时选择两种具体 Handler 实现之一:// 摘自UsbDeviceManager构造函数 if (mUsbGadgetHal == null) { // 初始化传统UsbHandler mHandler = new UsbHandlerLegacy(FgThread.get().getLooper(), mContext, this, alsaManager, permissionManager); } else { // 初始化基于HAL的UsbHandler mHandler = new UsbHandlerHal(FgThread.get().getLooper(), mContext, this, alsaManager, permissionManager); }UsbHandlerHal:现代设备使用,设备具备 IUsbGadget AIDL HAL。调用 HAL 的setCurrentUsbFunctions()请求配置变更,HAL 内部完成内核 ConfigFS 操作。UsbHandlerLegacy:旧设备无 gadget HAL 时的回退方案。直接写 sysfs 文件与系统属性切换 USB 功能。39.2.4 基于消息的状态机UsbHandler 通过 Android Handler 消息队列处理 USB 状态迁移。所有状态变更串行在前台线程执行,避免竞态条件。消息 ID常量触发来源0MSG_UPDATE_STATE内核上报连接 / 断开 / 配置完成1MSG_ENABLE_ADB开发者选项开关 ADB2MSG_SET_CURRENT_FUNCTIONS应用请求切换功能3MSG_SYSTEM_READYsystem server 就绪4MSG_BOOT_COMPLETED开机完成广播5MSG_USER_SWITCHED切换当前用户6MSG_UPDATE_USER_RESTRICTIONS用户策略变更7MSG_UPDATE_PORT_STATEType‑C 端口状态变更8MSG_ACCESSORY_MODE_ENTER_TIMEOUT配件协商 10 秒超时9MSG_UPDATE_CHARGING_STATE电池充电状态变更10MSG_UPDATE_HOST_STATE主机模式设备插拔11MSG_LOCALE_CHANGED语言变更(更新通知)12MSG_SET_SCREEN_UNLOCKED_FUNCTIONS屏幕解锁时功能偏好13MSG_UPDATE_SCREEN_LOCK锁屏显示 / 隐藏14MSG_SET_CHARGING_FUNCTIONS切换至仅充电模式15MSG_SET_FUNCTIONS_TIMEOUT功能切换超时16MSG_GET_CURRENT_USB_FUNCTIONS查询当前 gadget 功能17MSG_FUNCTION_SWITCH_TIMEOUTgadget 重新枚举超时18MSG_GADGET_HAL_REGISTEREDHAL 服务就绪19MSG_RESET_USB_GADGET重置 gadget 硬件20MSG_ACCESSORY_HANDSHAKE_TIMEOUTAOA 握手超时21MSG_INCREASE_SENDSTRING_COUNT收到 AOA 字符串描述符22MSG_UPDATE_USB_SPEEDUSB 速率协商完成23MSG_UPDATE_HAL_VERSIONHAL 版本信息更新24MSG_USER_UNLOCKED_AFTER_BOOT开机后首次解锁39.2.5 USB 状态迁移内核通过 UEvent 消息上报 USB 状态变更。UsbUEventObserver 接收消息并转换为 Handler 消息。UsbHandler 的updateState()将内核状态字符串映射为内部状态:// 摘自UsbHandler.updateState() if ("DISCONNECTED".equals(state)) { connected = 0; configured = 0; } else if ("CONNECTED".equals(state)) { connected = 1; configured = 0; } else if ("CONFIGURED".equals(state)) { connected = 1; configured = 1; }39.2.6 功能切换用户或系统请求切换 USB 功能时,状态机执行多步流程:39.2.7 防抖与超时功能切换会带来短暂 USB 断开。状态机会做防抖处理,避免虚假断开事件干扰切换流程。// UsbDeviceManager中防抖延时 private static final int DEVICE_STATE_UPDATE_DELAY_EXT = 3000; // 3秒 private static final int DEVICE_STATE_UPDATE_DELAY = 1000; // 1秒 private static final int HOST_STATE_UPDATE_DELAY = 1000; // 1秒 private static final int ACCESSORY_REQUEST_TIMEOUT = 10 * 1000; // 10秒 private static final int ACCESSORY_HANDSHAKE_TIMEOUT = 10 * 1000; // 10秒调用resetUsbGadget()之后,通过mResetUsbGadgetDisableDebounce临时关闭防抖,保证 gadget 重置后的第一次断开事件立即处理。39.2.8 与锁屏交互MTP 需要访问用户存储,锁屏时必须做保护。UsbDeviceManager 与锁屏模块协同工作:屏幕锁定(onKeyguardStateChanged(true)),Handler 收到 MSG_UPDATE_SCREEN_LOCK。如果 MTP 或 PTP 处于激活状态,Handler 切换为仅充电功能。用户偏好功能保存在 SharedPreferences,key 为usb‑screen‑unlocked‑config‑userId。屏幕解锁后,恢复之前保存的 USB 功能。39.2.9 接口黑名单出于安全考虑,当设备作为 USB 主机时,部分 USB 接口类禁止应用访问:// 摘自UsbDeviceManager静态代码块 sDenyInterfaces.add(UsbConstants.USB_CLASS_AUDIO); sDenyInterfaces.add(UsbConstants.USB_CLASS_COMM); sDenyInterfaces.add(UsbConstants.USB_CLASS_HID); sDenyInterfaces.add(UsbConstants.USB_CLASS_PRINTER); sDenyInterfaces.add(UsbConstants.USB_CLASS_MASS_STORAGE); sDenyInterfaces.add(UsbConstants.USB_CLASS_HUB); sDenyInterfaces.add(UsbConstants.USB_CLASS_CDC_DATA); sDenyInterfaces.add(UsbConstants.USB_CLASS_CSCID); sDenyInterfaces.add(UsbConstants.USB_CLASS_CONTENT_SEC); sDenyInterfaces.add(UsbConstants.USB_CLASS_VIDEO); sDenyInterfaces.add(UsbConstants.USB_CLASS_WIRELESS_CONTROLLER);39.2.10 MTP 服务绑定MTP/PTP 功能激活时,Handler 绑定 MTP 服务:// UsbHandler常量 protected static final String MTP_PACKAGE_NAME = "com.android.mtp"; protected static final String MTP_SERVICE_CLASS_NAME = "com.android.mtp.MtpService";ServiceConnection 在 MTP 会话整个生命周期保持绑定;切换离开 MTP/PTP 时解除绑定。该绑定除了启动服务外还有关键作用:阻止 ActivityManager 冻结 MTP 进程。如果没有绑定,系统可能为回收资源冻结 MTP 服务进程,打断正在进行的 USB 传输。39.2.11 MIDI 功能探测MIDI gadget 功能启用后,UsbDeviceManager 需要探测合成 MIDI 设备对应的 ALSA 卡号与设备号,有两种实现方式。现代方案(基于 sysfs 解析):// 遍历UDC控制器下sysfs目录 File soundDir = new File("/sys/class/udc/" + controllerName + "/gadget/sound"); File[] cardDirs = FileUtils.listFilesOrEmpty(soundDir, (dir, file) - file.startsWith("card")); File[] midis = FileUtils.listFilesOrEmpty(cardDirs[0], (dir, file) - file.startsWith("midi")); // 从"midiCcardDdevice"解析卡号设备号 Pattern pattern = Pattern.compile("midiC(\\d+)D(\\d+)"); Matcher matcher = pattern.matcher(midis[0].getName()); if (matcher.matches()) { mMidiCard = Integer.parseInt(matcher.group(1)); mMidiDevice = Integer.parseInt(matcher.group(2)); }传统方案(读取 ALSA 文件):// 读取旧版sysfs路径 Scanner scanner = new Scanner(new File(MIDI_ALSA_PATH)); mMidiCard = scanner.nextInt(); mMidiDevice = scanner.nextInt();探测得到的卡号、设备号传给 UsbAlsaManager,向 Android MIDI 服务注册外设 MIDI 设备。39.2.12 USB 状态广播Handler 向所有接收者广播 USB 状态变更:protected void updateUsbStateBroadcastIfNeeded(long functions) { Intent intent = new Intent(UsbManager.ACTION_USB_STATE); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND | Intent.FLAG_RECEIVER_FOREGROUND); intent.putExtra(UsbManager.USB_CONNECTED, mConnected); intent.putExtra(UsbManager.USB_HOST_CONNECTED, mHostConnected); intent.putExtra(UsbManager.USB_CONFIGURED, mConfigured); intent.putExtra(UsbManager.USB_DATA_UNLOCKED, isUsbTransferAllowed() isUsbDataTransferActive(mCurrentFunctions)); // 添加激活功能标记 long remainingFunctions = functions; while (remainingFunctions != 0) { intent.putExtra(UsbManager.usbFunctionsToString( Long.highestOneBit(remainingFunctions)), true); remainingFunctions -= Long.highestOneBit(remainingFunctions); } // 仅状态真正变化才广播 if (!isUsbStateChanged(intent)) return; sendStickyBroadcast(intent); }ACTION_USB_STATE是粘性广播:晚注册的接收者会立刻收到上一次广播状态。Intent 携带各个激活功能的布尔值,接收方可以单独检查某一个功能状态。39.2.13 用户限制执行企业管控设备可通过UserManager.DISALLOW_USB_FILE_TRANSFER限制 USB 文件传输:protected boolean isUsbTransferAllowed() { UserManager userManager = (UserManager) mContext.getSystemService( Context.USER_SERVICE); return !userManager.hasUserRestriction( UserManager.DISALLOW_USB_FILE_TRANSFER); }该限制生效时:MTP、PTP 功能被屏蔽USB 通知显示 “仅充电”应用无法切换至数据传输功能39.2.14 AOA 配件握手跟踪Handler 记录 AOA 握手过程详细时序信息,用于调试与统计:private long mAccessoryConnectionStartTime = 0L; // 收到GET_PROTOCOL时刻 private int mSendStringCount = 0; // SEND_STRING uevent计数 private boolean mStartAccessory = false; // 是否收到START // 广播握手详情用于调试 private void broadcastUsbAccessoryHandshake() { Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_HANDSHAKE) .putExtra(UsbManager.EXTRA_ACCESSORY_UEVENT_TIME, mAccessoryConnectionStartTime) .putExtra(UsbManager.EXTRA_ACCESSORY_STRING_COUNT, mSendStringCount) .putExtra(UsbManager.EXTRA_ACCESSORY_START, mStartAccessory) .putExtra(UsbManager.EXTRA_ACCESSORY_HANDSHAKE_END, SystemClock.elapsedRealtime()); sendStickyBroadcast(intent); }39.2.15 RNDIS 网络共享集成RNDIS(USB 网络共享)激活时:Handler 配置 RNDIS gadget 功能。基于ro.serialno生成本地管理 MAC 地址:// 首字节设0x02,表示本地管理地址 address[0] = 0x02; String serial = SystemProperties.get("ro.serialno", "1234567890ABCDEF"); // USB序列号异或填充剩余5字节 for (int i = 0; i serialLength; i++) { address[i % (ETH_ALEN - 1) + 1] ^= (int) serial.charAt(i); }
返回列表