十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android开发实战:U盘路径获取与文件操作全解析

Android开发实战:U盘路径获取与文件操作全解析 1. Android U盘路径获取的核心挑战在Android开发中获取外接U盘路径看似简单实则暗藏玄机。不同于PC系统直接挂载到固定路径Android设备对USB存储设备的处理方式因版本差异而大不相同。我曾在多个商业项目中处理过U盘读写需求发现开发者常陷入以下误区认为所有Android版本都能通过/mnt/usb或/storage路径直接访问忽略Android 10的存储沙盒限制未正确处理USB设备的动态插拔事件混淆了MANAGE_EXTERNAL_STORAGE和READ_EXTERNAL_STORAGE权限的使用场景2. 基础环境配置与权限处理2.1 AndroidManifest关键配置首先需要在AndroidManifest.xml中添加必要的权限声明和硬件特性!-- 必须声明USB主机功能 -- uses-feature android:nameandroid.hardware.usb.host / !-- 基础存储权限 -- uses-permission android:nameandroid.permission.READ_EXTERNAL_STORAGE/ uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE/ !-- Android 11需要此权限才能访问所有存储 -- uses-permission android:nameandroid.permission.MANAGE_EXTERNAL_STORAGE android:minSdkVersion30 / !-- 处理USB设备插拔的Intent过滤器 -- intent-filter action android:nameandroid.hardware.usb.action.USB_DEVICE_ATTACHED / /intent-filter meta-data android:nameandroid.hardware.usb.action.USB_DEVICE_ATTACHED android:resourcexml/device_filter /特别注意从Android 11开始Google强制要求使用Storage Access Framework(SAF)或申请MANAGE_EXTERNAL_STORAGE权限才能访问外部存储。但后者上架Google Play时需要特殊声明。2.2 动态权限申请实战在代码中需要分层处理运行时权限// 检查并申请MANAGE_EXTERNAL_STORAGE权限 fun checkStoragePermission(activity: Activity): Boolean { return if (Build.VERSION.SDK_INT Build.VERSION_CODES.R) { Environment.isExternalStorageManager().also { hasPermission - if (!hasPermission) { val intent Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) activity.startActivity(intent) } } } else { ContextCompat.checkSelfPermission( activity, Manifest.permission.WRITE_EXTERNAL_STORAGE ) PackageManager.PERMISSION_GRANTED } } // 处理USB设备权限 fun requestUsbPermission(device: UsbDevice, context: Context) { val usbManager context.getSystemService(Context.USB_SERVICE) as UsbManager val permissionIntent PendingIntent.getBroadcast( context, 0, Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) usbManager.requestPermission(device, permissionIntent) }3. USB设备检测与路径解析3.1 使用UsbManager枚举设备val usbManager getSystemService(Context.USB_SERVICE) as UsbManager val deviceList usbManager.deviceList.values deviceList.forEach { device - // 筛选大容量存储设备 if (device.interfaceCount.any { it.interfaceClass UsbConstants.USB_CLASS_MASS_STORAGE }) { Log.d(USB, 发现存储设备: ${device.deviceName}) // 检查是否已有权限 if (usbManager.hasPermission(device)) { processUsbDevice(device) } else { requestUsbPermission(device, this) } } }3.2 广播接收器实现需要注册广播接收器处理USB事件private val usbReceiver object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when (intent.action) { UsbManager.ACTION_USB_DEVICE_ATTACHED - { val device intent.getParcelableExtraUsbDevice(UsbManager.EXTRA_DEVICE) device?.let { onUsbAttached(it) } } UsbManager.ACTION_USB_DEVICE_DETACHED - { val device intent.getParcelableExtraUsbDevice(UsbManager.EXTRA_DEVICE) device?.let { onUsbDetached(it) } } ACTION_USB_PERMISSION - { val granted intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) val device intent.getParcelableExtraUsbDevice(UsbManager.EXTRA_DEVICE) device?.let { onUsbPermissionResult(it, granted) } } } } } // 注册广播 val filter IntentFilter().apply { addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED) addAction(UsbManager.ACTION_USB_DEVICE_DETACHED) addAction(ACTION_USB_PERMISSION) } registerReceiver(usbReceiver, filter)4. 实际路径获取方案对比4.1 方案一使用StorageManager推荐fun getUsbPaths(context: Context): ListString { val storageManager context.getSystemService(Context.STORAGE_SERVICE) as StorageManager val storageVolumes storageManager.storageVolumes return storageVolumes.mapNotNull { volume - if (volume.isRemovable volume.state Environment.MEDIA_MOUNTED) { // 实际路径可能类似/storage/XXXX-XXXX volume.directory?.absolutePath } else null } }4.2 方案二扫描挂载点fun scanMountPoints(): ListString { return File(/proc/mounts).readLines() .filter { it.contains(/storage) || it.contains(/mnt) } .map { it.split( )[1] } .distinct() }4.3 方案三使用libaums开源库在app/build.gradle中添加依赖implementation me.jahnen.libaums:core:0.10.0 implementation me.jahnen.libaums:libusbcommunication:0.3.0使用示例val devices UsbMassStorageDevice.getMassStorageDevices(context) devices.forEach { device - try { device.init() val partitions device.partitions partitions.forEach { partition - val fileSystem partition.fileSystem val rootDir fileSystem.rootDirectory // 遍历U盘文件 rootDir.listFiles().forEach { file - Log.d(USB, Found file: ${file.name}) } } } catch (e: Exception) { Log.e(USB, Error accessing USB, e) } }5. 实战中的坑与解决方案5.1 路径兼容性问题不同厂商设备挂载路径可能不同三星设备/storage/USB_DISK0华为设备/mnt/usb_storage小米设备/storage/udisk解决方案fun findBestUsbPath(context: Context): String? { return listOfNotNull( getFromStorageManager(context), scanForCommonMountPoints(), tryLibAumsFallback(context) ).firstOrNull() }5.2 文件系统兼容性常见问题NTFS格式U盘在部分设备上只读exFAT需要内核支持FAT32有4GB文件大小限制处理建议fun checkFilesystemCompat(usbFile: File): Boolean { return try { val testFile File(usbFile, .probe) testFile.createNewFile() // 尝试创建文件 testFile.delete() // 尝试删除文件 true } catch (e: Exception) { false } }5.3 大文件传输优化当处理视频等大文件时fun copyLargeFile(src: UsbFile, dest: File) { val input UsbFileStreamFactory.createBufferedInputStream(src) val output FileOutputStream(dest) val buffer ByteArray(1024 * 1024) // 1MB buffer var bytesRead: Int var totalBytes 0L while (input.read(buffer).also { bytesRead it } ! -1) { output.write(buffer, 0, bytesRead) totalBytes bytesRead updateProgress(totalBytes, src.length()) } input.close() output.flush() output.close() }6. 完整工具类实现以下是经过多个项目验证的U盘工具类object UsbHelper { private const val TAG UsbHelper private const val ACTION_USB_PERMISSION com.example.USB_PERMISSION private var usbReceiver: BroadcastReceiver? null private var isReceiverRegistered false fun init(context: Context, callback: (UsbDevice, Boolean) - Unit) { val usbManager context.getSystemService(Context.USB_SERVICE) as UsbManager usbReceiver object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val device intent.getParcelableExtraUsbDevice(UsbManager.EXTRA_DEVICE) when (intent.action) { ACTION_USB_PERMISSION - { val granted intent.getBooleanExtra( UsbManager.EXTRA_PERMISSION_GRANTED, false ) device?.let { callback(it, granted) } } UsbManager.ACTION_USB_DEVICE_ATTACHED - { device?.let { requestUsbPermission(it, context) } } } } } val filter IntentFilter().apply { addAction(ACTION_USB_PERMISSION) addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED) addAction(UsbManager.ACTION_USB_DEVICE_DETACHED) } context.registerReceiver(usbReceiver, filter) isReceiverRegistered true // 检查已连接的设备 usbManager.deviceList.values.forEach { device - if (isMassStorage(device)) { if (usbManager.hasPermission(device)) { callback(device, true) } else { requestUsbPermission(device, context) } } } } fun release(context: Context) { usbReceiver?.let { if (isReceiverRegistered) { try { context.unregisterReceiver(it) } catch (e: Exception) { Log.e(TAG, Error unregistering receiver, e) } } } } private fun requestUsbPermission(device: UsbDevice, context: Context) { val usbManager context.getSystemService(Context.USB_SERVICE) as UsbManager val permissionIntent PendingIntent.getBroadcast( context, 0, Intent(ACTION_USB_PERMISSION).apply { putExtra(UsbManager.EXTRA_DEVICE, device) }, PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) usbManager.requestPermission(device, permissionIntent) } private fun isMassStorage(device: UsbDevice): Boolean { for (i in 0 until device.interfaceCount) { if (device.getInterface(i).interfaceClass UsbConstants.USB_CLASS_MASS_STORAGE) { return true } } return false } fun getUsbRootPath(context: Context, device: UsbDevice): String? { return if (Build.VERSION.SDK_INT Build.VERSION_CODES.N) { val storageManager context.getSystemService(Context.STORAGE_SERVICE) as StorageManager storageManager.storageVolumes .firstOrNull { it.isRemovable } ?.directory?.absolutePath } else { // 兼容旧版本的回退方案 val mountFile File(/proc/mounts) if (mountFile.exists()) { mountFile.readLines() .firstOrNull { it.contains(device.deviceName) } ?.split( )?.get(1) } else null } } }使用示例class MainActivity : AppCompatActivity() { private lateinit var usbHelper: UsbHelper override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) usbHelper UsbHelper().apply { init(thisMainActivity) { device, granted - if (granted) { val path getUsbRootPath(thisMainActivity, device) path?.let { runOnUiThread { Toast.makeText( thisMainActivity, USB mounted at: $path, Toast.LENGTH_LONG ).show() } // 开始处理U盘文件 processUsbFiles(it) } } } } } override fun onDestroy() { super.onDestroy() usbHelper.release(this) } }7. 性能优化与安全建议缓存U盘路径避免频繁扫描挂载点private val usbPathCache mutableMapOfString, String() fun getCachedUsbPath(deviceId: String): String? { return usbPathCache[deviceId] ?: run { val path findUsbPath(deviceId) path?.let { usbPathCache[deviceId] it } path } }安全文件操作fun sanitizePath(inputPath: String): String { return inputPath.replace(../, ) .replace(//, /) .replace(Regex([^a-zA-Z0-9_./-]), ) }后台服务处理大文件class UsbCopyService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { intent?.getStringExtra(usb_path)?.let { path - thread { // 执行文件操作 copyUsbContents(File(path)) stopSelf(startId) } } return START_NOT_STICKY } }多线程优化fun parallelCopy(files: ListFile, destDir: File) { val executor Executors.newFixedThreadPool(4) files.forEach { file - executor.submit { file.copyTo(File(destDir, file.name), overwrite true) } } executor.shutdown() }在实际项目中U盘路径获取只是第一步后续的文件操作、异常处理、性能优化才是真正的挑战。特别是在Android 10的沙盒限制下开发者需要更精细地设计存储访问架构。建议在开发初期就建立完善的测试矩阵覆盖不同Android版本和设备厂商的特殊情况。
返回列表