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

Android Kotlin如何将文件保存到公共存储?

解决API30+写入公共Documents目录的问题

核心原因

Android 11(API30)起启用分区存储机制,直接通过File API写入公共共享目录(如/storage/emulated/0/Documents)会被系统禁止,即使在Manifest中声明WRITE_EXTERNAL_STORAGE权限也无效。

方案1:使用MediaStore写入公共Documents目录

这种方式无需额外权限(API30+无需申请存储权限),系统自动处理文件存储授权,适合直接写入指定公共目录的场景。以下是Kotlin实现代码:

工具类代码

import android.content.ContentValues
import android.content.Context
import android.net.Uri
import android.os.Build
import android.provider.MediaStore

fun appendTextToPublicDocument(context: Context, fileName: String, content: String) {
    val contentValues = ContentValues().apply {
        put(MediaStore.Downloads.DISPLAY_NAME, fileName)
        put(MediaStore.Downloads.MIME_TYPE, "text/plain")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // 指定Documents下的AltiPro子目录,不存在会自动创建
            put(MediaStore.Downloads.RELATIVE_PATH, "Documents/AltiPro")
            put(MediaStore.Downloads.IS_PENDING, 1)
        }
    }

    val uri = context.contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
    uri?.let {
        try {
            context.contentResolver.openOutputStream(it).use { outputStream ->
                outputStream?.let { stream ->
                    // 读取已有内容并追加新内容(API30+支持直接读取)
                    val existingContent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        context.contentResolver.openInputStream(it)?.bufferedReader()?.readText() ?: ""
                    } else {
                        // API29及以下可补充旧逻辑,此处优先适配API30+
                        ""
                    }
                    stream.write((existingContent + content).toByteArray())
                }
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                contentValues.clear()
                contentValues.put(MediaStore.Downloads.IS_PENDING, 0)
                context.contentResolver.update(it, contentValues, null, null)
            }
        } catch (e: Exception) {
            e.printStackTrace()
            // 可在此添加写入失败的提示逻辑
        }
    }
}

使用示例

在Activity/Fragment中直接调用:

val fileName = "AltiPro${System.currentTimeMillis()}.txt"
val content = "${location.time},${location.latitude},${location.longitude},$AltitudeInt\n"
appendTextToPublicDocument(this, fileName, content)

方案2:存储访问框架(SAF)让用户选择存储位置

如果需要让用户自行指定保存目录,可使用SAF实现,步骤如下:

1. 启动文件选择器

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.provider.DocumentsContract

class MainActivity : AppCompatActivity() {
    private val CREATE_FILE_REQUEST = 1001

    fun launchFileCreator() {
        val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
            addCategory(Intent.CATEGORY_OPENABLE)
            type = "text/plain"
            putExtra(Intent.EXTRA_TITLE, "AltiPro${System.currentTimeMillis()}.txt")
            // 默认打开Documents目录
            putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI)
        }
        startActivityForResult(intent, CREATE_FILE_REQUEST)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == CREATE_FILE_REQUEST && resultCode == RESULT_OK) {
            data?.data?.let { uri ->
                // 保存Uri到SharedPreferences,方便后续追加内容
                getSharedPreferences("AppPrefs", MODE_PRIVATE).edit()
                    .putString("saved_file_uri", uri.toString())
                    .apply()
                // 写入初始内容
                appendTextToUri(this, uri, "${location.time},${location.latitude},${location.longitude},$AltitudeInt\n")
            }
        }
    }
}

2. 追加内容到选中文件

fun appendTextToUri(context: Context, uri: Uri, content: String) {
    try {
        // "wa"模式表示追加写入
        context.contentResolver.openOutputStream(uri, "wa")?.use { outputStream ->
            outputStream.write(content.toByteArray())
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

注意事项

  • 方案1生成的文件会出现在系统「文件」应用的Documents/AltiPro目录下,用户可直接分享至Excel或Google Earth。
  • 若需支持API29及以下版本,需在Manifest中添加WRITE_EXTERNAL_STORAGE权限,并在运行时申请权限。

内容的提问来源于stack exchange,提问作者Jerome Jeanneret

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 23:46:17