最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android外接U盤(pán)的操作實(shí)踐

 更新時(shí)間:2025年08月21日 08:33:11   作者:沒(méi)有了遇見(jiàn)  
本文總結(jié)了在 Android(開(kāi)發(fā)板 Android 14)環(huán)境下外接 U 盤(pán)的操作實(shí)踐,涵蓋了從配置、權(quán)限申請(qǐng)、USB 設(shè)備識(shí)別,到文件系統(tǒng)讀取和本地文件復(fù)制的完整流程,本文內(nèi)容適合需要在 Android 上進(jìn)行 U 盤(pán)數(shù)據(jù)處理的開(kāi)發(fā)者參考和實(shí)踐,需要的朋友可以參考下

引言

本文總結(jié)了在 Android(開(kāi)發(fā)板 Android 14)環(huán)境下外接 U 盤(pán)的操作實(shí)踐,涵蓋了從配置、權(quán)限申請(qǐng)、USB 設(shè)備識(shí)別,到文件系統(tǒng)讀取和本地文件復(fù)制的完整流程。文章重點(diǎn)介紹了高版本 Android 的存儲(chǔ)權(quán)限處理、BroadcastReceiver 異步回調(diào)機(jī)制,以及利用開(kāi)源庫(kù) libaums 安全高效地讀取 U 盤(pán)文件的實(shí)現(xiàn)方式。同時(shí)提供了遞歸復(fù)制 U 盤(pán)目錄到本地的工具類及進(jìn)度回調(diào)接口,實(shí)現(xiàn)了對(duì)視頻、音樂(lè)等大文件的穩(wěn)定復(fù)制。本文內(nèi)容適合需要在 Android 上進(jìn)行 U 盤(pán)數(shù)據(jù)處理的開(kāi)發(fā)者參考和實(shí)踐。

操作流程

  • 配置
  • 權(quán)限
  • 獲取外接設(shè)備列表
  • 獲取根目錄
  • 復(fù)制文件到本地

1:使用USB外接設(shè)備的配置

使用USB的時(shí)候的配置分為,權(quán)限 過(guò)濾文件

1.1 權(quán)限配置

<uses-feature android:name="android.hardware.usb.host" />

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Android 10+ 需要申請(qǐng) 存儲(chǔ)訪問(wèn)權(quán)限,Android 11+ 可以使用 MANAGE_EXTERNAL_STORAGE 或 SAF(Storage Access Framework)。

1.2 過(guò)濾USB類型文件配置

<application
...
<meta-data
    android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
    android:resource="@xml/device_filter" />
    
 </application>

device_filter.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-device vendor-id="0x1234" product-id="0x5678" />
</resources>

注意:

  • 不配置會(huì)處理所有 USB Mass Storage 設(shè)備
  • 配置可以避免鼠標(biāo)、鍵盤(pán)等干擾

我這里沒(méi)處理這個(gè)配置,是獲取判斷外接設(shè)備后 只處理U盤(pán)的處理方式

2:讀取U盤(pán)數(shù)據(jù)

讀取U盤(pán)的具體步驟

  • 申請(qǐng)U盤(pán)權(quán)限
  • 獲取U盤(pán)數(shù)據(jù)
  • Copy數(shù)據(jù)

2.1 獲取U盤(pán)信息

在 Android 中申請(qǐng) U 盤(pán)權(quán)限通常是通過(guò) UsbManager + PendingIntent + BroadcastReceiver 來(lái)完成的

2.1.1 權(quán)限申請(qǐng)的方法

fun requestPermission(context: Context, usbManager: UsbManager, device: UsbDevice?) {
 val intent = Intent(MediaShowConstant.ACTION_USB_PERMISSION).apply {
            putExtra(UsbManager.EXTRA_DEVICE, device)
        }

        // Android 12+ 要求 PendingIntent 必須是 MUTABLE
        val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
        } else {
            PendingIntent.FLAG_UPDATE_CURRENT
        }

        val permissionIntent = PendingIntent.getBroadcast(context, 0, intent, flags)

        Log.d("UsbHelper", "申請(qǐng) U 盤(pán)權(quán)限: ${device.deviceName}")
        usbManager.requestPermission(device, permissionIntent)
}

2.1.2 廣播監(jiān)聽(tīng)

class CustomUsbPermissionReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val action = intent.action ?: return
        val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
        val device = intent.getParcelableExtra<UsbDevice>(UsbManager.EXTRA_DEVICE) ?: return

        Log.d("UsbHelper", "收到廣播: $action, device=${device.deviceName}")

        when (action) {
            UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
                if (!isMassStorageDevice(device)) return
                requestPermission(context, usbManager, device)
            }
            MediaShowConstant.ACTION_USB_PERMISSION -> {
                val granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)
                pendingDeviceIds.remove(device.deviceId)
                if (granted && isMassStorageDevice(device)) {
                    Log.d("UsbHelper", "USB 權(quán)限已授權(quán),開(kāi)始加載文件系統(tǒng)")
                    loadUsbFile(context, device)
                } else {
                    Log.w("UsbHelper", "USB 權(quán)限被拒絕: ${device.deviceName}")
                }
            }
            UsbManager.ACTION_USB_DEVICE_DETACHED -> {
                if (isMassStorageDevice(device) && initializedDeviceIds.remove(device.deviceId)) {
                    Log.d("UsbHelper", "U盤(pán)拔出,移除初始化標(biāo)記: ${device.deviceName}")
                    usbRemovedCallback?.invoke(device)
                }
            }
        }
    }
}

2.2 獲取數(shù)據(jù)

判斷權(quán)限

private fun checkUsbDevices(context: Context) {
    val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
    for (device in usbManager.deviceList.values) {
        if (!isMassStorageDevice(device)) continue
        if (!usbManager.hasPermission(device)) {
            requestPermission(context, usbManager, device)
        } else {
            loadUsbFile(context, device)
        }
    }
}

獲取U盤(pán)數(shù)據(jù)

private fun loadUsbFile(context: Context, device: UsbDevice) {
    Log.d("UsbHelper", "嘗試加載 USB: ${device.deviceName}")

    val devices = UsbMassStorageDevice.getMassStorageDevices(context)
    for (usbDevice in devices) {
        if (usbDevice.usbDevice.deviceId != device.deviceId) continue
        try {
            Log.d("UsbHelper", "初始化 USB: ${device.deviceName}")
            usbDevice.init()

            // 只加載第一個(gè)分區(qū)(如需多分區(qū)可遍歷 partitions)
            val fs = usbDevice.partitions[0].fileSystem
            initializedDeviceIds.add(device.deviceId)

            Log.d("UsbHelper", "調(diào)用回調(diào),U盤(pán)已初始化")
            loadFileCallback?.invoke(fs, fs.rootDirectory)
        } catch (e: Exception) {
            Log.e("UsbHelper", "USB 初始化異常: ${e.message}", e)
        }
    }
}

注意:

獲取u盤(pán)數(shù)據(jù)用的libaums 開(kāi)源庫(kù)

implementation  'me.jahnen.libaums:core:0.10.0'

3.文件Copy

將U盤(pán)識(shí)別和文件讀取分裝成一個(gè)兩個(gè)工具類.

3.1 盤(pán)識(shí)別工具類

import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager import android.hardware.usb.UsbConstants import android.hardware.usb.UsbDevice import android.hardware.usb.UsbManager import android.os.Build import android.os.Handler import android.os.Looper import android.util.Log import com.wkq.common.util.showToast import me.jahnen.libaums.core.UsbMassStorageDevice import me.jahnen.libaums.core.fs.FileSystem import me.jahnen.libaums.core.fs.UsbFile

object UsbPermissionHelper {
// 廣播接收器
private var usbPermissionReceiver: CustomUsbPermissionReceiver? = null
// 待申請(qǐng)權(quán)限的設(shè)備集合,避免重復(fù)申請(qǐng)
private val pendingDeviceIds = mutableSetOf<Int>()
// 已初始化的設(shè)備集合,方便拔出處理
private val initializedDeviceIds = mutableSetOf<Int>()
// U盤(pán)文件加載回調(diào)
var loadFileCallback: ((fs: FileSystem, rootDir: UsbFile) -> Unit)? = null
// U盤(pán)拔出回調(diào)
var usbRemovedCallback: ((device: UsbDevice) -> Unit)? = null
// 廣播是否已注冊(cè)
private var isReceiverRegistered = false

/**
 * 入口方法:處理 USB 權(quán)限和文件系統(tǒng)加載
 */
fun processUsb(context: Context, loadFileCallback: (fs: FileSystem, rootDir: UsbFile) -> Unit) {
    if (isReceiverRegistered) return

    this.loadFileCallback = loadFileCallback
    val appContext = context.applicationContext

    // 檢查設(shè)備是否支持 USB HOST
    if (!appContext.packageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
        appContext.showToast("設(shè)備不支持 USB HOST")
        return
    }

    // 注冊(cè)廣播接收器
    usbPermissionReceiver = CustomUsbPermissionReceiver()
    val filter = IntentFilter().apply {
        addAction(MediaShowConstant.ACTION_USB_PERMISSION) // 自定義權(quán)限廣播
        addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED)   // U盤(pán)插入廣播
        addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)   // U盤(pán)拔出廣播
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        appContext.registerReceiver(usbPermissionReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
    } else {
        @Suppress("DEPRECATION")
        appContext.registerReceiver(usbPermissionReceiver, filter)
    }
    isReceiverRegistered = true

    // 檢查當(dāng)前已連接的 USB 設(shè)備
    checkUsbDevices(appContext)
}

/**
 * 檢查當(dāng)前已連接的 USB 設(shè)備并處理
 */
private fun checkUsbDevices(context: Context) {
    val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
    for (device in usbManager.deviceList.values) {
        if (!isMassStorageDevice(device)) continue  // 只處理存儲(chǔ)類設(shè)備
        if (!usbManager.hasPermission(device)) {
            requestPermission(context, usbManager, device) // 請(qǐng)求權(quán)限
        } else {
            loadUsbFile(context, device) // 已有權(quán)限直接加載
        }
    }
}

/**
 * 判斷設(shè)備是否為 USB 存儲(chǔ)類
 */
private fun isMassStorageDevice(device: UsbDevice): Boolean {
    for (i in 0 until device.interfaceCount) {
        if (device.getInterface(i).interfaceClass == UsbConstants.USB_CLASS_MASS_STORAGE) return true
    }
    return false
}

/**
 * 加載 USB 文件系統(tǒng)
 */
private fun loadUsbFile(context: Context, device: UsbDevice) {
    Log.d("UsbHelper", "嘗試加載 USB: ${device.deviceName}")

    val devices = UsbMassStorageDevice.getMassStorageDevices(context)
    for (usbDevice in devices) {
        if (usbDevice.usbDevice.deviceId != device.deviceId) continue
        try {
            Log.d("UsbHelper", "初始化 USB: ${device.deviceName}")
            usbDevice.init() // 初始化 USB 設(shè)備

            // 只加載第一個(gè)分區(qū)(如需多分區(qū)可遍歷 partitions)
            val fs = usbDevice.partitions[0].fileSystem
            initializedDeviceIds.add(device.deviceId) // 標(biāo)記已初始化

            Log.d("UsbHelper", "調(diào)用回調(diào),U盤(pán)已初始化")
            loadFileCallback?.invoke(fs, fs.rootDirectory) // 回調(diào)文件系統(tǒng)
        } catch (e: Exception) {
            Log.e("UsbHelper", "USB 初始化異常: ${e.message}", e)
        }
    }
}

/**
 * 釋放資源
 */
fun release(context: Context) {
    val appContext = context.applicationContext
    if (isReceiverRegistered && usbPermissionReceiver != null) {
        try { appContext.unregisterReceiver(usbPermissionReceiver) } catch (_: IllegalArgumentException) {}
        usbPermissionReceiver = null
        isReceiverRegistered = false
    }
    loadFileCallback = null
    usbRemovedCallback = null
    pendingDeviceIds.clear()
    initializedDeviceIds.clear()
}

/**
 * 請(qǐng)求 USB 權(quán)限
 */
fun requestPermission(context: Context, usbManager: UsbManager, device: UsbDevice?) {
    device ?: return
    if (pendingDeviceIds.contains(device.deviceId)) return

    pendingDeviceIds.add(device.deviceId)
    Handler(Looper.getMainLooper()).postDelayed({
        val intent = Intent(MediaShowConstant.ACTION_USB_PERMISSION).apply {
            putExtra(UsbManager.EXTRA_DEVICE, device)
        }

        // Android 12+ 要求 PendingIntent 必須是 MUTABLE
        val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
        } else {
            PendingIntent.FLAG_UPDATE_CURRENT
        }

        val permissionIntent = PendingIntent.getBroadcast(context, 0, intent, flags)

        Log.d("UsbHelper", "申請(qǐng) U 盤(pán)權(quán)限: ${device.deviceName}")
        usbManager.requestPermission(device, permissionIntent)
    }, 200) // 延遲 200ms 避免廣播未注冊(cè)
}

/**
 * 自定義廣播接收器
 */
class CustomUsbPermissionReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val action = intent.action ?: return
        val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
        val device = intent.getParcelableExtra<UsbDevice>(UsbManager.EXTRA_DEVICE) ?: return

        Log.d("UsbHelper", "收到廣播: $action, device=${device.deviceName}")

        when (action) {
            UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
                if (!isMassStorageDevice(device)) return
                requestPermission(context, usbManager, device) // 插入時(shí)請(qǐng)求權(quán)限
            }
            MediaShowConstant.ACTION_USB_PERMISSION -> {
                val granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)
                pendingDeviceIds.remove(device.deviceId)
                if (granted && isMassStorageDevice(device)) {
                    Log.d("UsbHelper", "USB 權(quán)限已授權(quán),開(kāi)始加載文件系統(tǒng)")
                    loadUsbFile(context, device) // 權(quán)限允許后加載文件
                } else {
                    Log.w("UsbHelper", "USB 權(quán)限被拒絕: ${device.deviceName}")
                }
            }
            UsbManager.ACTION_USB_DEVICE_DETACHED -> {
                if (isMassStorageDevice(device) && initializedDeviceIds.remove(device.deviceId)) {
                    Log.d("UsbHelper", "U盤(pán)拔出,移除初始化標(biāo)記: ${device.deviceName}")
                    usbRemovedCallback?.invoke(device) // 回調(diào)拔出事件
                }
            }
        }
    }
}
}

3.2 文件復(fù)制工具類

import android.os.Handler
import android.os.Looper
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import me.jahnen.libaums.core.fs.FileSystem
import me.jahnen.libaums.core.fs.UsbFile
import me.jahnen.libaums.core.fs.UsbFileStreamFactory
import java.io.File
import java.io.FileOutputStream

object UsbFileCopyUtil {

    private const val TAG = "UsbFileCopyUtil"

    /**
     * 復(fù)制整個(gè) USB 目錄(遞歸方式),保證視頻和音樂(lè)文件都能正常復(fù)制
     */
    suspend fun copyUsbDirToCache(
        usbDir: UsbFile,
        fs: FileSystem,
        targetDir: File,
        filter: ((UsbFile) -> Boolean)? = null,
        callback: CopyProgressCallback? = null
    ): Boolean = withContext(Dispatchers.IO) {
        try {
            if (!usbDir.isDirectory) return@withContext false

            // 確保目標(biāo)根目錄存在
            if (!targetDir.exists() && !targetDir.mkdirs()) {
                Log.e(TAG, "Failed to create targetDir: ${targetDir.absolutePath}")
                return@withContext false
            }

            // 收集所有待復(fù)制文件
            val fileList = mutableListOf<Pair<UsbFile, File>>()
            collectUsbFiles(usbDir, targetDir, filter, fileList)

            // 回調(diào)總數(shù)(在主線程)
            Handler(Looper.getMainLooper()).post {
                callback?.onStart(fileList.size)
            }

            var current = 0
            var allSuccess = true

            for ((source, target) in fileList) {
                try {
                    // 創(chuàng)建父目錄
                    val parent = target.parentFile
                    if (parent != null && !parent.exists() && !parent.mkdirs()) {
                        Log.e(TAG, "Failed to create parent directory: ${parent.absolutePath}")
                        allSuccess = false
                        continue
                    }

                    // 跳過(guò)同名且大小相同的文件
                    if (target.exists() && target.length() == source.length) {
                        Log.d(TAG, "Skip same-size file: ${target.absolutePath}")
                        current++
                        Handler(Looper.getMainLooper()).post {
                            callback?.onFileCopied(current, fileList.size, source, target)
                        }
                        continue
                    }

                    // 安全文件名
                    val safeName = source.name.replace("[\\/:*?"<>|]".toRegex(), "_")
                    val safeTarget = File(target.parentFile, safeName)

                    // 手動(dòng)循環(huán)讀取,確保所有文件(包括 MP3)都能完整寫(xiě)入
                    UsbFileStreamFactory.createBufferedInputStream(source, fs).use { input ->
                        FileOutputStream(safeTarget).use { output ->
                            val buffer = ByteArray(64 * 1024)
                            var read: Int
                            while (true) {
                                read = input.read(buffer)
                                if (read == -1) break
                                output.write(buffer, 0, read)
                            }
                            output.flush()
                        }
                    }

                    current++
                    Handler(Looper.getMainLooper()).post {
                        callback?.onFileCopied(current, fileList.size, source, safeTarget)
                    }

                } catch (e: Exception) {
                    allSuccess = false
                    Log.e(TAG, "Failed to copy file: ${source.name}, target=${target.absolutePath}", e)
                    Handler(Looper.getMainLooper()).post {
                        callback?.onError(source, e)
                    }
                }
            }

            // 完成回調(diào)
            Handler(Looper.getMainLooper()).post {
                callback?.onComplete(allSuccess)
            }

            return@withContext allSuccess

        } catch (e: Exception) {
            Log.e(TAG, "Failed to copy USB directory", e)
            Handler(Looper.getMainLooper()).post {
                callback?.onError(usbDir, e)
                callback?.onComplete(false)
            }
            return@withContext false
        }
    }

    /**
     * 遞歸收集 USB 文件夾內(nèi)所有文件,保持目錄層級(jí)一致
     */
    private fun collectUsbFiles(
        usbDir: UsbFile,
        targetDir: File,
        filter: ((UsbFile) -> Boolean)?,
        outList: MutableList<Pair<UsbFile, File>>
    ) {
        for (child in usbDir.listFiles()) {
            if (filter != null && !filter(child)) continue

            val target = File(targetDir, child.name)
            if (child.isDirectory) {
                collectUsbFiles(child, target, filter, outList)
            } else {
                outList.add(child to target)
            }
        }
    }

    interface CopyProgressCallback {
        fun onStart(totalCount: Int)
        fun onFileCopied(current: Int, total: Int, source: UsbFile, target: File)
        fun onError(source: UsbFile, e: Exception)
        fun onComplete(success: Boolean)
    }
}

3.3 調(diào)用示例

UsbPermissionHelper.processUsb(this) { fs, rootDir ->
    val files = rootDir.listFiles()
    files.iterator().forEach {
        Log.d("UsbHelper:", "File: " + it.name)
    }
    val usbDir = rootDir.search("MediaFolder")
    if (usbDir != null && usbDir.isDirectory && usbDir.listFiles().size > 0) {
     copyFile(fs, usbDir)
    } else {
        this.showToast("The USB is not recognized to contain available files");
    }

}

注意:

  • 高版本的Android 的存讀取權(quán)限的申請(qǐng).
  • 讀文件三方庫(kù)
    • implementation 'me.jahnen.libaums:core:0.10.0'

總結(jié)

U盤(pán)數(shù)據(jù)讀取分為 配置,權(quán)限申請(qǐng) 獲取數(shù)據(jù) 復(fù)制文件幾步.這里總結(jié)了一下,因?yàn)槭情_(kāi)發(fā)板Android 14實(shí)現(xiàn),所以其他版本只能遇到了再處理。

以上就是Android外接U盤(pán)的操作實(shí)踐的詳細(xì)內(nèi)容,更多關(guān)于Android外接U盤(pán)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論

东源县| 财经| 大洼县| 楚雄市| 鹤岗市| 平远县| 长沙市| 孝昌县| 冷水江市| 仙桃市| 苍山县| 陆川县| 龙南县| 涿鹿县| 阳泉市| 习水县| 淮阳县| 明光市| 论坛| 长武县| 靖安县| 满城县| 清镇市| 太湖县| 和田县| 修水县| 酒泉市| 中山市| 苗栗县| 西林县| 饶阳县| 建瓯市| 安塞县| 军事| 颍上县| 东源县| 白水县| 太原市| 天台县| 丹江口市| 山丹县|