尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
第 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); }
RELATED

相关推荐

AirFlow空气质量预测模型:上下文保持与多速率状态建模解析

AirFlow空气质量预测模型:上下文保持与多速率状态建模解析

城市空气质量预测一直是个“看着简单、做好极难”的问题。你打开任何一款天气 App 都能看到未来几天的 PM2.5、 PM10、臭氧预报,但很少有人追问:这些数字背后,模型到底是怎么处理监测站数据不连续、污染物扩散随时间变化、不同站点采样频率不…

📅 2026/11/9 19:31:06
移动App发版避坑指南:签名、版本号与自动化检查全攻略

移动App发版避坑指南:签名、版本号与自动化检查全攻略

做移动开发这几年,如果说哪个环节最让我焦虑,那一定是发版这件事。平时写代码、做需求、修Bug,反馈链路都很短,代码有问题跑一次就能发现。但发版不一样,它是一场跨开发、测试、产品、运营的联合行为,任何一…

📅 2026/11/9 19:31:07
Vue3从零入门完整指南:核心语法、组件通信与实战项目

Vue3从零入门完整指南:核心语法、组件通信与实战项目

最近有不少刚开始学前端的小伙伴问我:都 2026 年了,新手学 Vue 到底该学 Vue2 还是直接学 Vue3?我的建议非常明确: 直接上手 Vue3 。Vue3 是当前 Vue 生态的绝对主流,社区组件库、企业级项目、招聘要求都已经全面转向…

📅 2026/10/19 22:30:12
MORE NEWS

更多资讯

📰

弱电电工证操作电压千万别踩坑:新乡水利工程从业者必看备考攻略

弱电电工证操作电压千万别踩坑:新乡水利工程从业者必看备考攻略 你是不是正为找工作发愁?手里缺个特种作业证,企业一问“有证吗”,你就只能摇头?弱电电工证操作电压这事儿,不是你想考就能考,也不是你想过就能过的,稍有不慎,钱花了、时间耗了,证还拿…

📰

电工证在哪考沈阳的通过率揭秘:别交完钱又白搭

电工证在哪考沈阳的通过率揭秘:别交完钱又白搭 怕考不过白交培训费?沈阳电工证考试别踩这些坑,备考有策略,通过率高一倍!今天咱们就聊聊【电工证在哪考沈阳的】这事儿,帮你避开陷阱,稳稳拿下证书。 电工证到底有什么用?…

📰

广东梅州哪里有电工证 复审延期别慌 真实费用全公开

广东梅州哪里有电工证 复审延期别慌 真实费用全公开 证书过期了不知道怎么复审延期?这几乎是所有特种作业人员都遇到过的头疼事。别慌,今天就带你从头到尾理清楚广东梅州电工证的办理流程、复审费用以及避坑要点。不管是低压还是高压电工证,现在掌握一套…

📰

浙江低压电工证怎么办值不值得考?怕被中介坑?这些真相你知道吗

浙江低压电工证怎么办值不值得考?怕被中介坑?这些真相你知道吗 你是不是也经常听到身边人说“浙江低压电工证怎么办”,但一听到要报名就犯愁?不知道该去哪儿报名,更怕被中介坑了钱还拿不到证?其实,这证值不值得考,答案就在你手里。今天就用真实案例,…

📰

证书过期了不知道怎么复审延期?2026河南最新政策解读及国网高压电工证多少钱

证书过期了不知道怎么复审延期?2026河南最新政策解读及国网高压电工证多少钱 证书过期了不知道怎么复审延期?2026河南最新政策已经明确,特种作业操作证复审流程、考试内容以及费用都有了新的变化。尤其是 国网高压电工证多少钱…

📰

襄县在哪办电工证怎么转异地官方报名入口在哪

襄县在哪办电工证怎么转异地官方报名入口在哪 之前考的证在外省能不能转过来?这可能是很多襄县小伙伴在办理电工证时最关心的问题。别急,我来告诉你怎么操作,还有官方报名入口在哪,让你少走弯路。 襄县电工证异地转接流程 首先, 襄县在哪办电工证…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬