Android12 U盘插拔链路源码全解析(四):Framework层(上) —— UsbHostManager

系列目录第一篇:全景图与调用链路概览 | 第二篇:内核层---USB驱动与uevent | 第三篇:Native层---vold与NetlinkManager | 第四篇:Framework层(上)---UsbHostManager | 第五篇:Framework层(下)---StorageManagerService | 第六篇:广播分发与SystemUI响应 | 第七篇:应用层---MediaScanner与SAF | 第八篇:实战调试与案例分析


一、引言

前三篇我们走完了"存储挂载链"从 Kernel 到 vold 的路径。但还有一条 并行的 USB 设备感知链 完全独立运作:它不关心分区、不关心文件系统,只负责回答一个核心问题------"现在插着的是什么 USB 设备?"

这条链的主角就是 UsbHostManager

本文将从 UsbService 启动、JNI 桥接、usbDeviceAttached() 全过程、UsbDevice 对象树构建,到广播发送,完整拆解 Java 框架层如何感知 USB 设备。


二、UsbService 启动链路

2.1 SystemServer 中的启动点

源码路径frameworks/base/services/java/com/android/server/SystemServer.java

java 复制代码
private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) {
    // ...
    t.traceBegin("StartUsbService");
    mSystemServiceManager.startService(UsbService.Lifecycle.class);
    t.traceEnd();
    // ...
}

SystemServer 在 startBootstrapServices() 阶段启动 UsbService,这比大多数应用层服务都要早。

2.2 UsbService 构造函数

源码路径frameworks/base/services/usb/java/com/android/server/usb/UsbService.java

java 复制代码
public class UsbService extends IUsbManager.Stub {

    private UsbService(Context context) {
        mContext = context;
        mHasUsbAccessory = false;
        mPackageManager = context.getPackageManager();

        // ★ 创建 UsbHostManager(核心)
        mHostManager = new UsbHostManager(context);
        // ★ 创建 UsbDeviceManager(管理手机作为 USB 设备的角色)
        mDeviceManager = new UsbDeviceManager(context);
        // ★ 创建 UsbPortManager(管理 Type-C 端口角色切换)
        mPortManager = new UsbPortManager(context);
        // ★ 创建 UsbAlsaManager(USB 音频设备管理)
        mAlsaManager = new UsbAlsaManager(context);
        // ★ 创建 UsbSettingsManager(用户/应用 USB 权限管理)
        mSettingsManager = new UsbSettingsManager(context);
    }

    // SystemService 的生命周期包装
    public static class Lifecycle extends SystemService {
        @Override
        public void onBootPhase(int phase) {
            if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
                // ★ 在 AMS 就绪后,UsbHostManager 开始工作
                mUsbService.systemReady();
            }
        }
    }

    public void systemReady() {
        mHostManager.systemReady();
        mDeviceManager.systemReady();
        mPortManager.systemReady();
        mAlsaManager.systemReady();
    }
}

五大子模块职责:

模块 职责
UsbHostManager ★ Host 模式核心:感知 USB 设备插入/拔出、广播
UsbDeviceManager Device 模式:手机作为 U 盘/充电等角色切换
UsbPortManager Type-C 端口管理:角色协商(DFP/UFP/DRP)
UsbAlsaManager USB 音频设备(耳机、声卡)管理
UsbSettingsManager 用户/应用 USB 权限授权管理

三、UsbHostManager 构造函数与 JNI 桥

3.1 构造函数

源码路径frameworks/base/services/usb/java/com/android/server/usb/UsbHostManager.java

java 复制代码
public class UsbHostManager {
    private static final String TAG = "UsbHostManager";

    // ★ 存储当前连接的设备(以 deviceName 为 key)
    private final HashMap<String, UsbDevice> mDevices = new HashMap<>();

    // ★ 设备的 USB 类过滤黑名单
    private final HashSet<Integer> mBlacklistedInterfaces;
    private final HashSet<Integer> mBlacklistedDevices;

    private final Context mContext;
    private final Object mLock = new Object();

    public UsbHostManager(Context context) {
        mContext = context;
        mBlacklistedInterfaces = new HashSet<>();
        mBlacklistedDevices = new HashSet<>();

        // 从资源文件读取黑名单配置
        // 例如 HID 键盘/鼠标通常由 Input 系统处理,不需要 UsbHostManager 重复广播
        String[] blacklist = context.getResources()
            .getStringArray(com.android.internal.R.array.config_usbHostBlacklist);
        for (String s : blacklist) {
            // 解析黑名单规则(如 "class:0x03" 表示 HID 类)
        }
    }
}

3.2 systemReady() ------ JNI 监听启动

java 复制代码
public void systemReady() {
    synchronized (mLock) {
        // ★ 启动 Native 层的 USB Host 总线监控
        // 这个 JNI 方法会阻塞等待设备事件
        new Thread("UsbService host thread") {
            @Override
            public void run() {
                // ★ 核心!进入 JNI 层,循环等待 USB 事件
                monitorUsbHostBus();
            }
        }.start();
    }
}

3.3 JNI 层:monitorUsbHostBus()

源码路径frameworks/base/services/core/jni/com_android_server_UsbHostManager.cpp

cpp 复制代码
static void android_server_UsbHostManager_monitorUsbHostBus(JNIEnv* env, jobject thiz) {
    // ★ 打开 /dev/bus/usb 目录,用 inotify 或轮询监听变化
    unique_fd fd(TEMP_FAILURE_RETRY(open(USB_FS_DIR, O_RDONLY | O_DIRECTORY)));

    while (!isDeviceDeleted) {
        // 1. 枚举所有 USB 总线
        for (const auto& bus : scandir(USB_FS_DIR)) {
            // 2. 枚举每个总线上的设备
            for (const auto& dev : scandir(bus.path())) {
                // 3. 读取设备描述符
                struct usb_device_descriptor desc;
                read(fd, &desc, sizeof(desc));

                // 4. ★ 回调 Java 层
                env->CallVoidMethod(thiz, method_usbDeviceAttached,
                    deviceName,         // 如 "/dev/bus/usb/001/003"
                    desc.idVendor,
                    desc.idProduct,
                    desc.bDeviceClass,
                    desc.bDeviceSubClass,
                    desc.bDeviceProtocol,
                    manufacturerName,
                    productName,
                    version,
                    serialNumber);
            }
        }
        // 5. 等待下一个设备变化事件(uevent 或 inotify)
        poll(...);
    }
}

关键点 :JNI 层并没有直接解析 uevent,而是通过 inotify 监听 /dev/bus/usb/ 目录 来感知设备变化。这与 vold 通过 netlink socket 监听 SUBSYSTEM=block uevent 是完全不同的通道。


四、usbDeviceAttached() ------ 完整源码拆解

这是 UsbHostManager 中 最重要的方法,也是最长的单个方法之一。

源码路径frameworks/base/services/usb/java/com/android/server/usb/UsbHostManager.java

java 复制代码
// ★ JNI 回调入口
private void usbDeviceAttached(String deviceName, int vendorID, int productID,
        int deviceClass, int deviceSubclass, int deviceProtocol,
        String manufacturerName, String productName, String version,
        String serialNumber) {

    // ========== 第一步:获取设备完整信息 ==========
    // 通过 nativeGetDeviceMembers() 获取接口、端点等详细信息
    UsbDescriptorParser.DeviceDescriptor deviceDesc =
            nativeGetDeviceMembers(deviceName);

    // ========== 第二步:黑名单过滤 ==========
    // 过滤掉不需要 UsbHostManager 处理的设备
    // 例如 HID 键盘/鼠标已经由 InputFlinger 接管
    if (shouldBlackList(deviceClass, deviceSubclass, deviceProtocol,
            manufacturerName, productName, version, serialNumber)) {
        return;  // ★ 被黑名单拦截,直接返回
    }

    // ========== 第三步:构建接口和端点描述符 ==========
    int numInterfaces = deviceDesc.getNumInterfaces();
    UsbInterface[] interfaces = new UsbInterface[numInterfaces];

    for (int i = 0; i < numInterfaces; i++) {
        UsbDescriptorParser.InterfaceDescriptor ifaceDesc =
                deviceDesc.getInterface(i);

        // 检查接口是否在黑名单中
        if (shouldBlackList(ifaceDesc.getInterfaceClass(),
                ifaceDesc.getInterfaceSubclass(),
                ifaceDesc.getInterfaceProtocol(), null, null, null, null)) {
            // 该接口被黑名单拦截,但设备本身可能还有其他可用接口
            ifaceDesc.setBlacklisted(true);
        }

        // 读取接口的供应商特定描述符
        byte[] rawDescs = deviceDesc.getRawDescriptors(i);

        // 构建端点数组
        int numEndpoints = ifaceDesc.getNumEndpoints();
        UsbEndpoint[] endpoints = new UsbEndpoint[numEndpoints];
        for (int e = 0; e < numEndpoints; e++) {
            UsbDescriptorParser.EndpointDescriptor endpointDesc =
                    ifaceDesc.getEndpoint(e);
            endpoints[e] = new UsbEndpoint(
                endpointDesc.getEndpointAddress(),  // 如 0x81(IN 端点)
                endpointDesc.getAttributes(),       // 如 0x02(Bulk 传输)
                endpointDesc.getMaxPacketSize(),    // 最大包大小
                endpointDesc.getInterval()          // 轮询间隔
            );
        }

        // ★ 构建 UsbInterface 对象
        interfaces[i] = new UsbInterface(
            ifaceDesc.getInterfaceNumber(),
            ifaceDesc.getAlternateSetting(),
            manufacturerName,
            productName,
            version,
            serialNumber,
            ifaceDesc.getInterfaceClass(),
            ifaceDesc.getInterfaceSubclass(),
            ifaceDesc.getInterfaceProtocol(),
            endpoints,
            rawDescs
        );
    }

    // ========== 第四步:构建 UsbDevice 对象树 ==========
    UsbDevice device = new UsbDevice(
        deviceName,
        vendorID,           // 厂商 ID,如 0x0781 (SanDisk)
        productID,          // 产品 ID,如 0x5591
        deviceClass,        // 设备类
        deviceSubclass,
        deviceProtocol,
        manufacturerName,   // "SanDisk"
        productName,        // "Ultra USB 3.0"
        version,            // USB 版本
        serialNumber,       // 序列号
        null,               // 配置(可为 null)
        interfaces,         // ★ 接口数组
        null, null          // 控制端点
    );

    // ========== 第五步:加入设备列表并发送广播 ==========
    synchronized (mLock) {
        // 5.1 检查是否重复
        if (mDevices.get(deviceName) != null) {
            Slog.w(TAG, "device already on mDevices list: " + deviceName);
            return;
        }

        // 5.2 加入 HashMap
        mDevices.put(deviceName, device);

        // 5.3 ★ 发送广播 ACTION_USB_DEVICE_ATTACHED
        Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        intent.putExtra(UsbManager.EXTRA_DEVICE, device);
        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
    }
}

4.1 黑名单过滤机制

java 复制代码
// config.xml 中配置的黑名单示例
// frameworks/base/core/res/res/values/config.xml
<string-array name="config_usbHostBlacklist">
    <!-- HID (Human Interface Device) - handled by InputFlinger -->
    <item>class:0x03</item>
    <!-- ADB Interface - handled by adbd -->
    <item>subclass:0x02,protocol:0x01</item>
</string-array>

黑名单规则支持以下匹配方式(任意匹配一项即拦截):

规则格式 含义 示例
class:0xXX 按设备类匹配 class:0x03 = HID
subclass:0xXX 按子类匹配 subclass:0x02
protocol:0xXX 按协议匹配 protocol:0x01
vid:0xXXXX 按厂商 ID 匹配 vid:0x046d = Logitech

五、UsbDevice 对象树结构

构建完成后的对象树层级关系:

复制代码
UsbDevice  (SanDisk Ultra USB 3.0)
  │ vendorID=0x0781, productID=0x5591
  │ deviceClass=0x00, serialNumber="4C530001260121106363"
  │
  └── UsbInterface[0]  (interfaceClass=0x08 Mass Storage)
        │  interfaceSubclass=0x06 (SCSI)
        │  interfaceProtocol=0x50 (Bulk-Only)
        │
        ├── UsbEndpoint (address=0x81, type=BULK, IN)
        │     → 数据输入(设备→主机)
        │
        └── UsbEndpoint (address=0x02, type=BULK, OUT)
              → 数据输出(主机→设备)

这个树形结构被完整地序列化到 IntentEXTRA_DEVICE 中,通过 Binder 跨进程传递到接收广播的应用。


六、广播发送分析

6.1 Intent 构建

java 复制代码
// UsbManager.ACTION_USB_DEVICE_ATTACHED
// 实际值: "android.hardware.usb.action.USB_DEVICE_ATTACHED"

Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);

// ★ 携带完整的 UsbDevice 对象(实现了 Parcelable)
intent.putExtra(UsbManager.EXTRA_DEVICE, device);

// ★ 标记为前台广播(更快送达)
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);

// ★ 发送给所有用户
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);

6.2 FLAG_RECEIVER_FOREGROUND 的含义

Flag 优先级 适用场景
无 Flag(普通广播) 通用事件
FLAG_RECEIVER_FOREGROUND 实时性要求高的硬件事件
FLAG_RECEIVER_REGISTERED_ONLY - 仅动态注册的接收器

使用 FLAG_RECEIVER_FOREGROUND 意味着:

  1. 广播队列优先级最高,几乎即时送达
  2. 接收器运行在前台进程组
  3. 适合硬件热插拔这种对时效要求极高的场景

6.3 拔出流程 ------ usbDeviceDetached()

java 复制代码
private void usbDeviceDetached(String deviceName) {
    synchronized (mLock) {
        UsbDevice device = mDevices.remove(deviceName);
        if (device != null) {
            Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED);
            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
            intent.putExtra(UsbManager.EXTRA_DEVICE, device);
            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
        }
    }
}

拔出流程非常简单:从 mDevices 中移除、发送 ACTION_USB_DEVICE_DETACHED 广播。


七、与 vold 的并行对比

现在可以清晰地看到两条并行链路的设计差异:

维度 UsbHostManager 链路 vold + StorageManagerService 链路
感知方式 JNI inotify 监听 /dev/bus/usb/ netlink socket 监听 uevent
关注对象 USB 设备本身(vendor/product/class) Block 设备(分区/文件系统)
核心数据 UsbDeviceUsbInterfaceUsbEndpoint DiskPublicVolume
广播 USB_DEVICE_ATTACHED / DETACHED MEDIA_MOUNTED / MEDIA_UNMOUNTED
主要消费者 需要直接操作 USB 设备的 App 需要读写文件的 App / MediaScanner
典型场景 USB 摄像头、USB 串口、自定义 USB 配件 U 盘、移动硬盘

八、关键源码文件索引

复制代码
frameworks/base/services/
├── java/com/android/server/SystemServer.java
│       → startBootstrapServices() 中启动 UsbService
│
├── usb/java/com/android/server/usb/
│   ├── UsbService.java
│   │       → 五大子模块的容器
│   ├── UsbHostManager.java              ★ 本文主角
│   │       → usb
相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui