You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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}")
        }
    }
}

额外注意事项

  1. 权限配置:在AndroidManifest.xml中添加API 28及以下版本所需的存储权限:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
    

    同时需要在应用运行时动态申请该权限,可借助Capacitor的@capacitor/permissions插件实现。

  2. 空安全优化:移除原代码中的!!强制非空断言,改用?:处理参数为空的场景,避免空指针崩溃;使用use函数自动关闭流,确保资源正确释放。

  3. MIME类型:在Android 10+版本的ContentValues中添加MIME_TYPE字段,帮助系统正确识别文件类型,可根据实际存储的文件类型(如application/pdf、image/png等)调整。

内容的提问来源于stack exchange,提问作者Julian Lachniet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 01:35:15