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
}