Android MediaStore下载在API Level 28及以下失效如何解决?
问题:Android API 28及以下版本无法将文件保存到下载文件夹
我编写了一段基于Capacitor的Android插件代码,用于将文本数据存储到用户的下载文件夹。代码在Android 10(API Level 29)及以上版本可正常运行,但在API Level 28及以下版本调用download方法时触发错误。
原代码
@CapacitorPlugin(name = "Downloads") class DownloadsPlugin : Plugin() { @PluginMethod(returnType = PluginMethod.RETURN_NONE) fun download(call: PluginCall) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val filename = call.getString("fileName")!! val data = call.getString("data")!! val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, filename) put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) } val contentResolver = context.contentResolver val uri = contentResolver.insert(MediaStore.Files.getContentUri("external"), contentValues)!! val outputStream = contentResolver.openOutputStream(uri)!! outputStream.write(data.toByteArray(StandardCharsets.UTF_8)) } call.resolve() } }
错误日志
2022-10-08 16:10:59.064 23121-23184/com.example.example E/Capacitor: Serious error executing plugin java.lang.reflect.InvocationTargetException at java.lang.reflect.Method.invoke(Native Method) at com.getcapacitor.PluginHandle.invoke(PluginHandle.java:125) at com.getcapacitor.Bridge.lambda$callPluginMethod$0$com-getcapacitor-Bridge(Bridge.java:706) at com.getcapacitor.Bridge$$ExternalSyntheticLambda5.run(Unknown Source:8) at android.os.Handler.handleCallback(Handler.java:873) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:193) at android.os.HandlerThread.run(HandlerThread.java:65) Caused by: java.lang.IllegalArgumentException: no path was provided when inserting new file at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:165) at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135) at android.content.ContentProviderProxy.insert(ContentProviderNative.java:476) at android.content.ContentResolver.insert(ContentResolver.java:1587) at com.example.example.DownloadsPlugin.download(DownloadsPlugin.kt:36) at java.lang.reflect.Method.invoke(Native Method) at com.getcapacitor.PluginHandle.invoke(PluginHandle.java:125) at com.getcapacitor.Bridge.lambda$callPluginMethod$0$com-getcapacitor-Bridge(Bridge.java:706) at com.getcapacitor.Bridge$$ExternalSyntheticLambda5.run(Unknown Source:8) at android.os.Handler.handleCallback(Handler.java:873) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:193) at android.os.HandlerThread.run(HandlerThread.java:65)
解决方案
问题原因
API Level 28及以下版本不支持MediaStore.MediaColumns.RELATIVE_PATH字段,直接使用该字段插入MediaStore会触发"no path was provided"的参数错误。需要针对低版本系统采用直接操作文件的方式,同时处理存储权限。
修改后的完整代码
@CapacitorPlugin(name = "Downloads") class DownloadsPlugin : Plugin() { @PluginMethod(returnType = PluginMethod.RETURN_NONE) fun download(call: PluginCall) { // 处理参数为空的情况,避免空指针异常 val filename = call.getString("fileName") ?: run { call.reject("fileName参数不能为空") return } val data = call.getString("data") ?: run { call.reject("data参数不能为空") return } try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android 10+ 版本逻辑,保持原有的MediaStore方式 val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, filename) put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) put(MediaStore.MediaColumns.MIME_TYPE, "text/plain") // 根据实际文件类型调整MIME类型 } val contentResolver = context.contentResolver val uri = contentResolver.insert(MediaStore.Files.getContentUri("external"), contentValues) ?: run { call.reject("创建文件URI失败") return } // 使用use自动关闭输出流,避免资源泄漏 contentResolver.openOutputStream(uri)?.use { outputStream -> outputStream.write(data.toByteArray(StandardCharsets.UTF_8)) } ?: run { call.reject("打开输出流失败") return } } else { // API 28及以下版本逻辑:直接操作下载目录下的文件 val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) val targetFile = File(downloadsDir, filename) // 检查WRITE_EXTERNAL_STORAGE权限 if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { call.reject("需要WRITE_EXTERNAL_STORAGE权限") return } // 使用use自动关闭文件输出流 FileOutputStream(targetFile).use { outputStream -> outputStream.write(data.toByteArray(StandardCharsets.UTF_8)) } } // 操作成功,返回结果 call.resolve() } catch (e: Exception) { // 捕获异常并返回错误信息 call.reject("文件保存失败:${e.message}") } } }
额外注意事项
权限配置:在
AndroidManifest.xml中添加API 28及以下版本所需的存储权限:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />同时需要在应用运行时动态申请该权限,可借助Capacitor的
@capacitor/permissions插件实现。空安全优化:移除原代码中的
!!强制非空断言,改用?:处理参数为空的场景,避免空指针崩溃;使用use函数自动关闭流,确保资源正确释放。MIME类型:在Android 10+版本的
ContentValues中添加MIME_TYPE字段,帮助系统正确识别文件类型,可根据实际存储的文件类型(如application/pdf、image/png等)调整。
内容的提问来源于stack exchange,提问作者Julian Lachniet
相关产品推荐
相关产品推荐

