Android通过连接USB读写SD卡(libaums方案)

Android通过连接USB读写SD卡

最近有一个需求是要求通过Usb扩展读取到SD卡的内容。可以从Usb存储设备拷贝文件到内置卡,也可以从内置卡文件拷贝到Usb存储。

连接方式

1. 相关的引入包

 implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    implementation 'androidx.activity:activity-ktx:1.5.1'
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0"
    // 工具类
    implementation "com.blankj:utilcodex:1.30.0"
    // USB管理
    implementation 'me.jahnen.libaums:core:0.10.0'

2. Mainfest配置

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

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

    <uses-permission
        android:name="android.hardware.usb.host"
        android:required="true" />

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <!--Android 13 权限适配-->
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

    <application
        android:name=".MainApp"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:requestLegacyExternalStorage="true"
        android:supportsRtl="true"
        android:theme="@style/Theme.AndroidProGuard">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

MainApp

class MainApp : Application() {

    override fun onCreate() {
        super.onCreate()
        Utils.init(this)
    }
}

3. 读写Usb的文件

1. 先获取Usb文件系统列表

    /**
     * 获取USB文件系统列表
     */
    private fun getUsbFileSystem(): Array<FileSystem>? {
        // 判断是否有文件权限
        if (!hasFilePermission()) {
            requestFilePermission()
            return null
        }
        // 是否插入了USB设备
        val devices = UsbMassStorageDevice.getMassStorageDevices(this)
        if (devices.isEmpty()) {
            ToastUtils.showShort("没有插入USB存储设备")
            return null
        }
        // 判断是否有USB权限
        if (!hasUsbPermission(devices)) {
            requestUsbPermission(devices)
            return null
        }
        // 获取USB文件系统
        return devices.map {
            it.init()
            it.partitions[0].fileSystem
        }.toTypedArray()
    }

   /**
     * 是否有文件权限
     */
    private fun hasFilePermission(): Boolean {
        return when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> // Android 10以上
                return Environment.isExternalStorageManager()

            Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> // Android 6以上
                PermissionUtils.isGranted(
                    Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    Manifest.permission.READ_EXTERNAL_STORAGE
                )
            else -> true
        }
    }

  /**
     * 请求文件权限
     */
    private fun requestFilePermission() {
        when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {// Android 10以上
                if (!Environment.isExternalStorageManager()) {
                    AlertDialog.Builder(this)
                        .setTitle("提示")
                        .setMessage("请前往开启文件访问权限,否则无法使用此功能!")
                        .setNegativeButton(
                            "取消"
                        ) { dialog, _ ->
                            dialog.dismiss()
                        }
                        .setPositiveButton("前往") { dialog, _ ->
                            dialog.dismiss()
                            startActivity(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION))
                        }.create().show()

                }
            }
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {// Android 6以上
                if (!PermissionUtils.isGranted(
                        Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.READ_EXTERNAL_STORAGE
                    )
                ) {
                    PermissionUtils.permission(
                        Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.READ_EXTERNAL_STORAGE
                    ).callback { isAllGranted, _, _, _ ->
                        if (!isAllGranted) {
                            ToastUtils.showShort("没有开启文件权限")
                        }
                    }.request()
                }
            }
            else -> {}
        }
    }

  /**
     * 是否有USB权限
     */
    private fun hasUsbPermission(devices: Array<UsbMassStorageDevice>): Boolean {
        val usbManager = getSystemService(Context.USB_SERVICE) as UsbManager
        for (device in devices) {
            if (!usbManager.hasPermission(device.usbDevice)) {
                return false
            }
        }
        return true
    }

    /**
     * 请求USB权限
     */
    private fun requestUsbPermission(devices: Array<UsbMassStorageDevice>) {
        val permissionIntentFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            PendingIntent.FLAG_IMMUTABLE
        } else {
            PendingIntent.FLAG_ONE_SHOT
        }

        val permissionIntent = PendingIntent.getActivity(
            this,
            0,
            Intent(ACTION_USB_PERMISSION),
            permissionIntentFlag
        )
        val usbManager = getSystemService(Context.USB_SERVICE) as UsbManager
        devices.forEach {
            usbManager.requestPermission(it.usbDevice, permissionIntent)
        }
    }

2. 获取到的Usb文件系统进行读写操作(ViewModel中处理)

2.1. 从USB拷贝文件到内置卡
	private val sBufferSize = 524288

   /**
     * 从USB拷贝文件到内置卡
     */
    fun copyFileFromUsb(devices: Array<FileSystem>) {
        flow {
            if (devices.isEmpty()) {
                throw IllegalStateException("没有插入USB存储设备")
            }
            val sdPath = getExternalStorageDirectory()
            if (sdPath.isNullOrEmpty()) {
                throw IllegalStateException("没有文件读取权限")
            }
            val fileSystem = devices[0]
            val newFile = File(sdPath, "demo.jpeg")
            var copySuccess = false
            for (childFile in fileSystem.rootDirectory.listFiles()) {
                if (childFile.name == "demo.jpeg") {// 测试文件
                    copySuccess = copyUsbFile(newFile, childFile)
                }
            }
            emit(copySuccess)
        }.flowOn(Dispatchers.IO)
            .catch {
                ToastUtils.showShort("拷贝错误:$it")
            }.onEach {
                ToastUtils.showShort("拷贝${if (it) "成功" else "失败"}")
            }.launchIn(viewModelScope)
    }
    /**
     * 手机内置目录
     */
    private fun getExternalStorageDirectory(): String? {
        val extFileStatus = Environment.getExternalStorageState()
        val extFile = Environment.getExternalStorageDirectory()
        if (extFileStatus == Environment.MEDIA_MOUNTED && extFile.exists() && extFile.isDirectory
            && extFile.canWrite()
        ) {
            return extFile.absolutePath
        }
        return null
    }


  /**
     * 拷贝文件
     */
    private fun copyUsbFile(newFile: File, child: UsbFile): Boolean {
        var out: OutputStream? = null
        var inputStream: InputStream? = null
        try {
            FileUtils.createOrExistsFile(newFile)
            out = BufferedOutputStream(
                FileOutputStream(newFile)
            )
            inputStream = UsbFileInputStream(child)
            val bytes = ByteArray(sBufferSize)
            var count: Int
            var total: Long = 0
            while (inputStream.read(bytes).also { count = it } != -1) {
                out.write(bytes, 0, count)
                total += count.toLong()
            }
        } catch (e: Exception) {
            e.printStackTrace()
            return false
        } finally {
            try {
                out?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
            try {
                inputStream?.close()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        return true
    }

2.2. 读取Usb文件的内容
 private val sBufferSize = 524288

 /**
     * 读取文件
     */
    fun readFile(devices: Array<FileSystem>) {
        flow {
            if (devices.isEmpty()) {
                throw IllegalStateException("没有插入USB存储设备")
            }
            val fileSystem = devices[0]
            var text = ""
            for (childFile in fileSystem.rootDirectory.listFiles()) {
                if (childFile.name == "demo.txt") {// 测试文件
                    text = readFile2String(childFile) ?: ""
                }
            }
            emit(text)
        }.flowOn(Dispatchers.IO)
            .catch {
                ToastUtils.showShort("读取错误:$it")
            }.onEach {
                ToastUtils.showShort("读取内容:$it")
            }.launchIn(viewModelScope)
    }

    private fun readFile2String(file: UsbFile): String? {
        val bytes = readFile2BytesByStream(file) ?: return null
        return String(bytes)
    }

    private fun readFile2BytesByStream(file: UsbFile): ByteArray? {
        return try {
            var os: ByteArrayOutputStream? = null
            val usbFis: InputStream = UsbFileInputStream(file)
            try {
                os = ByteArrayOutputStream()
                val b = ByteArray(sBufferSize)
                var len: Int
                while (usbFis.read(b, 0, sBufferSize).also { len = it } != -1) {
                    os.write(b, 0, len)
                }
                os.toByteArray()
            } catch (e: IOException) {
                e.printStackTrace()
                null
            } finally {
                try {
                    usbFis.close()
                } catch (e: IOException) {
                    e.printStackTrace()
                }
                try {
                    os?.close()
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
            null
        }
    }

2.3. 在Usb写入文件
 /**
     * 写入文件
     */
    fun writeFile(devices: Array<FileSystem>) {
        flow {
            if (devices.isEmpty()) {
                throw IllegalStateException("没有插入USB存储设备")
            }
            val fileSystem = devices[0]
            val newFile = fileSystem.rootDirectory.createFile("hello.txt")
            val os = UsbFileOutputStream(newFile)
            os.write("Hello World".toByteArray())
            os.close()
            emit(true)
        }.flowOn(Dispatchers.IO)
            .catch {
                ToastUtils.showShort("写入错误:$it")
            }.onEach {
                ToastUtils.showShort("写入${if (it) "成功" else "失败"}")
            }.launchIn(viewModelScope)
    }
2.4. 内置卡文件拷贝到Usb存储

    /**
     * 拷贝文件到USB
     */
    fun copyFileToUsb(devices: Array<FileSystem>) {
        flow {
            if (devices.isEmpty()) {
                throw IllegalStateException("没有插入USB存储设备")
            }
            val sdPath = getExternalStorageDirectory()
            if (sdPath.isNullOrEmpty()) {
                throw IllegalStateException("没有文件读取权限")
            }
            val fileSystem = devices[0]
            val baseFile = File(sdPath, "Hello.jpg")
            val root = fileSystem.rootDirectory
            emit(copyFileToUsb(baseFile, root))
        }.flowOn(Dispatchers.IO)
            .catch {
                ToastUtils.showShort("拷贝错误:$it")
            }.onEach {
                ToastUtils.showShort("拷贝${if (it) "成功" else "失败"}")
            }.launchIn(viewModelScope)

    }

	 /**
     * 手机根目录
     */
    private fun getExternalStorageDirectory(): String? {
        val extFileStatus = Environment.getExternalStorageState()
        val extFile = Environment.getExternalStorageDirectory()
        if (extFileStatus == Environment.MEDIA_MOUNTED && extFile.exists() && extFile.isDirectory
            && extFile.canWrite()
        ) {
            return extFile.absolutePath
        }
        return null
    }

	private fun copyFileToUsb(baseFile: File, root: UsbFile): Boolean {
        if (!baseFile.exists()) return false
        var out: OutputStream? = null
        var inputStream: InputStream? = null
        try {
            val newUsbFile = root.createFile(baseFile.name)
            inputStream = FileInputStream(baseFile)

            out = BufferedOutputStream(
                UsbFileOutputStream(newUsbFile)
            )

            val bytes = ByteArray(sBufferSize)
            var count: Int
            var total: Long = 0
            while (inputStream.read(bytes).also { count = it } != -1) {
                out.write(bytes, 0, count)
                total += count.toLong()
            }
        } catch (e: Exception) {
            e.printStackTrace()
            return false
        } finally {
            try {
                out?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
            try {
                inputStream?.close()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        return true
    }

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值