1. Android USB OTG技术基础
USB OTG(On-The-Go)是Android设备作为主机直接连接U盘、鼠标等USB外设的技术标准。从Android 3.1开始系统原生支持,但实际开发中会遇到各种兼容性问题。我遇到过最典型的情况是:同一款U盘在三星手机上能正常识别,却在小米设备上提示"不支持的USB设备"。
这种差异主要源于三个层面:
- 硬件差异:不同厂商的USB控制器芯片对供电要求不同
- 内核驱动:Android系统对USB Mass Storage协议的支持程度
- 文件系统兼容性:FAT32普遍兼容,但NTFS需要额外内核模块
通过ls /dev/block命令可以查看已识别的存储设备,通常U盘会显示为sda或sdb开头的设备节点。但直接访问这些节点需要root权限,普通应用需要通过系统API间接操作。
2. 权限申请与设备检测
Android 6.0之后权限模型变化很大,需要动态申请READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限。但即使获得这些权限,对OTG设备的操作仍有限制。我在华为EMUI系统上就遇到过即使授权后仍无法写入的问题。
关键代码示例:
// 检查权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE); } // 注册USB设备插拔广播 IntentFilter filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); registerReceiver(usbReceiver, filter);设备检测的完整流程应该是:
- 获取UsbManager系统服务
- 检查已连接的USB设备列表
- 过滤出Mass Storage类设备
- 申请设备访问权限
3. 文件系统挂载原理
当U盘插入时,Android系统会经历以下过程:
- 内核识别USB设备并加载对应驱动
- vold服务检测到新存储设备
- 根据
/etc/vold.fstab配置尝试挂载 - 在
/mnt/media_rw下创建挂载点
通过mount命令可以看到类似这样的挂载信息:
/dev/block/sda1 /mnt/media_rw/AAAA-BBBB vfat rw,dirsync...但普通应用无法直接访问这些路径,这就是需要libaums等第三方库的原因。我做过测试,在Android 10上,系统原生挂载的U盘路径对应用是完全不可见的。
4. libaums框架实战
libaums通过JNI直接与Linux内核交互,绕过Android存储框架的限制。其核心类包括:
UsbMassStorageDevice:表示物理存储设备Partition:对应磁盘分区FileSystem:抽象文件系统操作UsbFile:类似Java的File类
典型使用流程:
// 初始化设备 UsbMassStorageDevice device = UsbMassStorageDevice.getMassStorageDevices(this)[0]; device.init(); // 获取文件系统 Partition partition = device.getPartitions().get(0); FileSystem fs = partition.getFileSystem(); // 文件操作 UsbFile root = fs.getRootDirectory(); UsbFile newFile = root.createFile("test.txt"); OutputStream os = UsbFileStreamFactory.createBufferedOutputStream(newFile); os.write("Hello OTG".getBytes()); os.close();实际使用中我发现几个注意点:
- 文件操作最好使用缓冲流,性能差异可达10倍
- 大文件传输时要定期检查设备连接状态
- NTFS支持需要单独编译内核模块
5. 性能优化与兼容性处理
通过实测发现不同文件系统类型的性能差异:
| 文件系统 | 写入速度 | 读取速度 | 兼容性 |
|---|---|---|---|
| FAT32 | 15MB/s | 28MB/s | ★★★★★ |
| exFAT | 18MB/s | 32MB/s | ★★★☆☆ |
| NTFS | 6MB/s | 12MB/s | ★★☆☆☆ |
兼容性问题的常见解决方案:
- 自动重试机制:当IO异常时延迟后重试
- 文件系统检测:拒绝挂载不支持的格式
- 备用写入路径:当OTG不可用时转存到内部存储
// 健壮性改进示例 public void safeWrite(UsbFile file, byte[] data) throws IOException { int retry = 0; while (retry < 3) { try { OutputStream os = UsbFileStreamFactory.createBufferedOutputStream(file); os.write(data); os.close(); return; } catch (IOException e) { if (++retry == 3) throw e; SystemClock.sleep(500); } } }6. 与SAF框架的对比
Storage Access Framework (SAF)是Google官方推荐的外置存储访问方案,但实测发现:
libaums优势:
- 直接文件系统访问,性能更好
- 不受系统UI限制,后台可操作
- 支持批量文件操作
SAF优势:
- 无需处理USB权限
- 系统级统一体验
- 支持所有存储提供商(如Google Drive)
选择建议:
- 需要后台服务处理文件 → libaums
- 需要用户交互选择文件 → SAF
- 系统应用开发 → 直接使用系统API
7. 高级功能实现
多分区支持:
for (Partition partition : device.getPartitions()) { FileSystem fs = partition.getFileSystem(); // 处理每个分区... }文件系统信息获取:
Log.d("FS Info", "Label: " + fs.getVolumeLabel() + "\nCapacity: " + fs.getCapacity() + "\nFree: " + fs.getFreeSpace());自定义挂载点(需root):
Process process = Runtime.getRuntime().exec("mount -t vfat /dev/block/sda1 /mnt/otg"); process.waitFor();在开发文件管理器类应用时,还需要考虑:
- 文件变化监听(通过轮询实现)
- 缩略图生成
- 文件排序与过滤
8. 常见问题排查
问题1:设备无法识别
- 检查USB主机模式是否启用
- 尝试更换OTG转接头
- 在设备上执行
dmesg查看内核日志
问题2:写入速度慢
- 改用BufferedOutputStream
- 增加写入缓冲区大小(建议8KB以上)
- 避免频繁的小文件写入
问题3:文件损坏
- 确保正确关闭文件流
- 实现写入校验机制
- 考虑使用临时文件+原子重命名
我在项目中遇到的真实案例:某次更新后用户反馈文件丢失,最后发现是未正确处理USB设备突然拔出的情况。解决方案是增加以下保护代码:
try { // 文件操作... } catch (DeviceDisconnectedException e) { showToast("存储设备已断开"); recoverPartialFiles(); }开发这类功能时,务必在不同品牌设备上进行测试,特别是华为、小米等深度定制系统。记得处理各种边缘情况,比如用户在使用过程中突然拔出U盘,或者设备电量不足导致写入中断等场景。