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-permissionandroid: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" /><applicationandroid: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"><activityandroid: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 UsbManagerfor (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 UsbManagerdevices.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 = falsefor (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? = nullvar inputStream: InputStream? = nulltry {FileUtils.createOrExistsFile(newFile)out = BufferedOutputStream(FileOutputStream(newFile))inputStream = UsbFileInputStream(child)val bytes = ByteArray(sBufferSize)var count: Intvar total: Long = 0while (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 nullreturn String(bytes)}private fun readFile2BytesByStream(file: UsbFile): ByteArray? {return try {var os: ByteArrayOutputStream? = nullval usbFis: InputStream = UsbFileInputStream(file)try {os = ByteArrayOutputStream()val b = ByteArray(sBufferSize)var len: Intwhile (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.rootDirectoryemit(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 falsevar out: OutputStream? = nullvar inputStream: InputStream? = nulltry {val newUsbFile = root.createFile(baseFile.name)inputStream = FileInputStream(baseFile)out = BufferedOutputStream(UsbFileOutputStream(newUsbFile))val bytes = ByteArray(sBufferSize)var count: Intvar total: Long = 0while (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}