Android中如何将Retrofit返回的ByteArray写入Excel并存储到SD卡?
解决Retrofit下载Excel文件为空且格式无效的问题
问题分析
你遇到的0KB空文件和格式错误提示(翻译后:
Excel无法打开文件“ExcelTest.xlsx”,因为文件格式或文件扩展名无效。请验证文件未损坏,且文件扩展名与文件格式匹配。
),核心是代码里的几个关键错误:
- 文件路径处理错误:直接拼接字符串生成文件路径,且打开文件时传入的是目录Uri而非文件Uri
- 空安全隐患:
it.data?.bytes()如果it.data为null,会写入空内容导致文件0KB - 高版本Android Uri兼容问题:7.0+使用
Uri.fromFile会触发FileUriExposedException,同时无法正确被其他应用访问 - 异常捕获范围过窄:仅捕获
FileNotFoundException,无法排查其他写入或IO错误
修正后的代码
1. 处理文件写入逻辑(修复路径、空安全、流写入优化)
try { mContext?.let { context -> // 获取外部存储的文档目录,比PICTURES更适合存放Excel文件 val parentDir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) ?: context.getExternalFilesDir(null) // 备选目录 // 创建目标文件对象,避免字符串拼接的路径错误 val excelFile = File(parentDir, "ExcelTest.xlsx") // 确保父目录存在 if (!parentDir.exists()) { parentDir.mkdirs() } // 检查ResponseBody非空,用流逐块写入(避免大文件OOM) it.data?.let { responseBody -> FileOutputStream(excelFile).use { outputStream -> responseBody.byteStream().use { inputStream -> // 用缓冲区复制流,效率更高 val buffer = ByteArray(4096) var bytesRead: Int while (inputStream.read(buffer).also { bytesRead = it } != -1) { outputStream.write(buffer, 0, bytesRead) } outputStream.flush() } } } ?: run { // 处理ResponseBody为空的情况 Log.e("ExcelDownload", "ResponseBody is null") return@let } // 打开Excel文件的正确逻辑 openExcelFile(context, excelFile) } } catch (e: IOException) { // 捕获所有IO异常并打印,方便调试 Log.e("ExcelDownload", "Write file failed", e) }
2. 实现兼容高版本的文件打开方法
private fun openExcelFile(context: Context, file: File) { val fileUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // Android 7.0+ 使用FileProvider生成Uri FileProvider.getUriForFile( context, "${context.packageName}.fileprovider", // 对应AndroidManifest里的authorities file ) } else { Uri.fromFile(file) } val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(fileUri, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK } // 检查是否有应用能处理该Intent if (intent.resolveActivity(context.packageManager) != null) { context.startActivity(intent) } else { Log.e("ExcelDownload", "No app available to open Excel file") } }
3. 配置FileProvider(AndroidManifest.xml)
如果是Android 7.0+,需要在Manifest里添加FileProvider配置:
<application ...> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application>
然后在res/xml目录下创建file_paths.xml:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-files-path name="external_files" path="." /> </paths>
关键优化点说明
- 使用
File对象而非字符串拼接路径,避免路径格式错误 - 用
use函数自动关闭流,无需手动调用close(),防止资源泄漏 - 逐块复制流而非
bytes()方法,避免大文件加载到内存导致OOM - 兼容Android 7.0+的FileProvider,解决Uri暴露问题
- 扩大异常捕获范围并打印日志,便于定位问题
- 检查目标目录是否存在,避免因目录不存在导致写入失败
内容的提问来源于stack exchange,提问作者Ajay gadadasu
相关产品推荐
相关产品推荐

