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

news2024/11/16 13:41:41

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
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/711988.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于PSO优化LSSVM的时序预测MATLAB实战

今天给大家分享PSO优化LSSVM的时序预测代码实战&#xff0c;主要从算法原理和代码实战展开。需要了解更多算法代码的&#xff0c;可以点击文章左下角的阅读全文&#xff0c;进行获取哦~需要了解智能算法、机器学习、深度学习和信号处理相关理论的可以后台私信哦&#xff0c;下一…

SQL-每日一题【196.删除重复的电子邮箱】

题目 表: Person 编写一个 SQL 删除语句来 删除 所有重复的电子邮件&#xff0c;只保留一个id最小的唯一电子邮件。 以 任意顺序 返回结果表。 &#xff08;注意&#xff1a; 仅需要写删除语句&#xff0c;将自动对剩余结果进行查询&#xff09; 查询结果格式如下所示。 示…

电脑密码忘了怎么解除?分享4个好方法!

我的电脑设置了一个密码&#xff0c;但刚刚想开电脑的时候怎么也想不到那个密码了&#xff01;电脑密码忘了怎么解除&#xff1f;有什么好的方法吗&#xff1f; 为电脑设置一个密码可以更好地保护我们的电脑数据。但有时候我们在输入密码时可能会忘记密码&#xff0c;这将导致我…

考虑学PMP认证的项目经理,听我一句劝

早上好&#xff0c;我是老原。昨天3月份的考试成绩出了&#xff0c;我的朋友圈跟过年似的。 参加完3月的考试的同学体感普遍不是很好&#xff0c;本以为大家能过就很优秀了嘛&#xff0c;没想到是3A满天飞…… 我结合自己和一些同学的备考经历&#xff0c;分享一些备考思路。 …

【c++】并行编程:OpenMP入门

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍OpenMP入门。 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下次更新不迷路&#x1f9…

“希尔排序:打破时间瓶颈的排序算法 “

文章目录 &#x1f50d;什么是希尔排序&#x1f511;希尔排序分组思想&#x1f4c8;希尔排序的优缺点&#x1f468;‍&#x1f4bb;希尔排序代码剖析 &#x1f50d;什么是希尔排序 希尔排序&#xff08;Shell Sort&#xff09;是插入排序的一种高效率的改进版本&#xff0c;也…

浏览器缓存方式有哪些(cookie、localstorage、sessionstorage)

浏览器缓存方式 概要 http缓存 基于HTTP协议的浏览器文件级缓存机制 websql 只有较新的chrome浏览器支持&#xff0c;并以一个独立规范形式出现 indexDB 一个为了能够在客户端存储可观数量的结构化数据&#xff0c;并且在这些数据上使用索引进行高性能检索的 API Cooki…

v-model双向绑定指令

文章目录 前言v-model.lazy 延迟同步v-model.trim 去掉空格 前言 v-model指令是Vue.js中实现双向数据绑定的一种重要机制。它可以将表单控件的值与Vue.js实例中的数据进行双向绑定&#xff0c;即当表单控件的值发生变化时&#xff0c;Vue.js实例中的数据也会随之更新&#xff…

qt实现漂亮主页面

模仿自feiyangqingyun的博客_CSDN博客-Qt/C控件SDK使用示例,Qt/C音视频开发,Qt/C自定义控件领域博主 1.无边框窗口可移动 #ifndef MOVABLE_WIDGET_H #define MOVABLE_WIDGET_H#include <QWidget>class movable_widget:public QWidget { public:movable_widget(QWidget *…

华为荣耀6X(BLN-AL20)解锁全过程

这台旧手机一直闲置&#xff0c;想用它做测试机&#xff0c;所以必须先解锁。在此之前我已将手机改成了直供电&#xff0c;所以图片里没有电池&#xff0c;但是目前直供电方案并不完美&#xff0c;除了直供电线要插&#xff0c;尾插也要插上&#xff0c;淘宝卖家给出的理由是普…

部署 CNI网络组件

部署 flannel K8S 中 Pod 网络通信&#xff1a; ●Pod 内容器与容器之间的通信 在同一个 Pod 内的容器&#xff08;Pod 内的容器是不会跨宿主机的&#xff09;共享同一个网络命令空间&#xff0c; 相当于它们在同一台机器上一样&#xff0c;可以用 localhost 地址访问彼此的端…

如何看待程序员的高薪现象?

点击上方关注 “终端研发部” 设为“星标”&#xff0c;和你一起掌握更多数据库知识 最近在知乎上看到这样个话题&#xff1a; 难道不应该吗&#xff1f; 本人月薪八千&#xff0c;在北京一线&#xff0c;拿着最基础的工资&#xff0c;上的加班最频繁的班&#xff0c;干最累的活…

STM32模拟SPI协议控制数字电位器MCP41010电阻值

STM32模拟SPI协议控制数字电位器MCP41010电阻值 MCP41010是单路8位分辨率数字电位器&#xff0c;通过SPI接口可控制电位器阻值分配&#xff0c;相当于PW0端在PA0和PB0之间滑动。如下图所示&#xff1a; MCP41010是10K欧姆规格的数字电位器&#xff0c;即PA0和PB0之间的阻值恒…

Spring Boot 中的 @HystrixCommand 注解

Spring Boot 中的 HystrixCommand 注解 简介 在分布式系统中&#xff0c;服务之间的调用是不可避免的。但随着服务数量的增加&#xff0c;服务之间的依赖关系也会变得越来越复杂&#xff0c;服务的故障也会变得越来越常见。一旦某个服务出现故障&#xff0c;它所依赖的服务也…

Helm之深入浅出Kubernetes包管理工具基础

Helm 基础 作者&#xff1a;行癫&#xff08;盗版必究&#xff09; 一&#xff1a;Helm 简介 1.简介 ​ Helm 是 Kubernetes 的包管理器&#xff1b;它提供了提供、共享和使用为 Kubernetes 构建的软件的能力&#xff1b;是CNCF的毕业项目&#xff0c;自 Helm 加入 CNCF 以来…

【Canal】从原理、配置出发,从0到1完成Canal搭建

文章目录 简介工作原理MySQL主备复制原理canal 工作原理 Canal架构Canal-HA机制应用场景同步缓存 Redis /全文搜索 ES下发任务数据异构 MySQL 配置开启 binlog扩展statementrowmixed 配置权限 Canal 配置配置启动报错解决 实战引入依赖代码样例测试 前几天在网上冲浪的时候发现…

MYSQL03高级_新增用户、授予权限、授权底层表结构、角色理解

文章目录 ①. 登录服务器操作②. 用户的增删改③. 修改用户密码④. MySQL8密码管理⑤. 权限列表及原则⑥. 授予查看回收权限⑦. 底层权限表操作⑧. 角色的理解 ①. 登录服务器操作 ①. 启动MySQL服务后,可以通过mysql命令来登录MySQL服务器,命令如下: mysql –h hostname|hos…

chatgpt赋能python:搜索Python答案的软件

搜索Python答案的软件 介绍&#xff1a;什么是搜索Python答案的软件&#xff1f; 搜索Python答案的软件是一种工具&#xff0c;可以帮助编程人员快速地找到他们在编写Python代码时遇到的问题的答案。这种软件可以搜索各种不同的网站&#xff0c;以帮助用户找到最适合他们问题…

实例006 菜级联菜单

实例说明 如果管理程序功能菜单非常多&#xff0c;一些功能中又包括许多子功能&#xff0c;这时可以使用级联菜单来组织系统的各个功能。实例运行结果如图1.6所示。 图1.6 级联菜单 技术要点 制作级联菜单需要使用MenuStrip控件。 注意&#xff1a;在使用级联菜单时最好不要…