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

Kotlin获取设备已有文件列表失败:仅读取到应用创建文件如何解决?

问题分析与解决方案

核心原因

你的问题出在Android 10(API 29)引入的分区存储(Scoped Storage)机制:默认情况下,APP只能访问自身创建的文件、媒体库中的文件,无法通过File API直接读取其他APP或手动导入的文件(比如你通过Android Studio导入的PDF)。同时你的Manifest权限配置存在错误:ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION、ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION是跳转系统设置的Intent Action,不是权限,需要删除。


解决方案

根据你的需求,提供两种可行方案:

方案一:适配分区存储(推荐,符合Google规范)

对于文档类文件(如PDF),使用MediaStore API查询,无需申请全文件访问权限,适合绝大多数APP场景。

步骤1:修正Manifest权限

保留必要权限,适配不同Android版本:

<!-- Android 12及以下需要,Android 13+用细分权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />
<!-- Android 13+读取文档需要的权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_DOCUMENTS"
    android:minSdkVersion="33" />

步骤2:动态申请权限+查询文件

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.provider.MediaStore
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import java.io.File
import android.os.Environment

class MainActivity : AppCompatActivity() {
    // 权限申请Launcher
    private lateinit var requestReadMediaLauncher: ActivityResultLauncher<String>
    private lateinit var requestReadStorageLauncher: ActivityResultLauncher<Array<String>>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        initPermissionLaunchers()
        checkPermissions()
    }

    private fun initPermissionLaunchers() {
        // Android 13+ 读取文档权限
        requestReadMediaLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
            if (granted) proceedWithOperations()
            else Toast.makeText(this, "需要读取文档权限才能访问PDF文件", Toast.LENGTH_SHORT).show()
        }

        // Android 6-12 读取存储权限
        requestReadStorageLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
            val isGranted = permissions[Manifest.permission.READ_EXTERNAL_STORAGE] ?: false
            if (isGranted) proceedWithOperations()
            else Toast.makeText(this, "需要读取存储权限才能访问文件", Toast.LENGTH_SHORT).show()
        }
    }

    private fun checkPermissions() {
        when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
                if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_DOCUMENTS) != PackageManager.PERMISSION_GRANTED) {
                    requestReadMediaLauncher.launch(Manifest.permission.READ_MEDIA_DOCUMENTS)
                } else proceedWithOperations()
            }
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
                if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                    requestReadStorageLauncher.launch(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE))
                } else proceedWithOperations()
            }
            else -> proceedWithOperations()
        }
    }

    private fun proceedWithOperations() {
        createFile()
        queryPdfFiles() // 查询所有PDF
        getFilesList(documentsPath) // 读取自身创建的文件
    }

    // 查询Documents目录下的所有PDF文件
    private fun queryPdfFiles() {
        val projection = arrayOf(
            MediaStore.Files.FileColumns.DISPLAY_NAME,
            MediaStore.Files.FileColumns.RELATIVE_PATH
        )

        val selection = "${MediaStore.Files.FileColumns.MIME_TYPE} = ? AND ${MediaStore.Files.FileColumns.RELATIVE_PATH} LIKE ?"
        val selectionArgs = arrayOf("application/pdf", "%Documents%")

        contentResolver.query(
            MediaStore.Files.getContentUri("external"),
            projection,
            selection,
            selectionArgs,
            null
        )?.use { cursor ->
            while (cursor.moveToNext()) {
                val fileName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME))
                println("PDF文件: $fileName")
            }
        }
    }

    // 原有代码逻辑
    private val externalStorage: String = Environment.getExternalStorageDirectory().absolutePath
    private val documentsDir: String = Environment.DIRECTORY_DOCUMENTS
    private val documentsPath: String = "$externalStorage/$documentsDir"

    private fun createFile() {
        val fileName = "test.txt"
        val file = File(documentsPath, fileName)
        file.writeText("testing file...")
    }

    private fun getFilesList(path: String) {
        File(path).walk()
            .forEach { println("WALK: $it") }
    }
}

方案二:申请全文件访问权限(仅适合文件管理器类APP)

如果你的APP是文件管理器这类需要访问所有文件的应用,可申请MANAGE_EXTERNAL_STORAGE权限,但该权限需要用户手动在系统设置中授权,且Google Play对这类权限的审核严格。

步骤1:修正Manifest权限

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
    tools:ignore="ScopedStorage" /> <!-- 忽略Lint警告 -->

步骤2:跳转系统设置授权

import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.Settings
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import java.io.File
import android.os.Environment

class MainActivity : AppCompatActivity() {
    private lateinit var requestManageStorageLauncher: ActivityResultLauncher<Intent>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        requestManageStorageLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Environment.isExternalStorageManager()) {
                proceedWithOperations()
            } else {
                Toast.makeText(this, "需要全文件访问权限才能读取所有文件", Toast.LENGTH_SHORT).show()
            }
        }

        checkManageStoragePermission()
    }

    private fun checkManageStoragePermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            if (!Environment.isExternalStorageManager()) {
                val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply {
                    data = Uri.parse("package:$packageName")
                }
                requestManageStorageLauncher.launch(intent)
            } else proceedWithOperations()
        } else {
            // Android 9及以下复用方案一的READ_EXTERNAL_STORAGE权限申请逻辑
        }
    }

    private fun proceedWithOperations() {
        createFile()
        getFilesList(documentsPath)
    }

    // 原有代码逻辑
    private val externalStorage: String = Environment.getExternalStorageDirectory().absolutePath
    private val documentsDir: String = Environment.DIRECTORY_DOCUMENTS
    private val documentsPath: String = "$externalStorage/$documentsDir"

    private fun createFile() {
        val fileName = "test.txt"
        val file = File(documentsPath, fileName)
        file.writeText("testing file...")
    }

    private fun getFilesList(path: String) {
        File(path).walk()
            .forEach { println("WALK: $it") }
    }
}

关键注意事项

  1. 分区存储是趋势:Google强烈推荐使用MediaStore或SAF(存储访问框架)访问文件,MANAGE_EXTERNAL_STORAGE权限仅开放给特定类型的APP。
  2. 权限适配版本:不同Android版本的权限机制差异较大,需针对性处理。
  3. 避免滥用权限:不要为了省事就申请全文件访问权限,否则可能无法通过Google Play审核。

内容的提问来源于stack exchange,提问作者Felipe Damaro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 22:20:47