【问题标题】:Android APIv29 FileNotFoundException EACCES (Permission denied)Android APIv29 FileNotFoundException EACCES(权限被拒绝)
【发布时间】:2020-02-14 05:22:45
【问题描述】:

在为 targetSdkVersion v29 构建时,我无法访问存储。

这是我的 gradle 配置:

    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    ...
        minSdkVersion 15
        targetSdkVersion 29

请注意,WRITE_EXTERNAL_STORAGE 已授予权限,并且在为 targetSdkVersion 28 构建时,相同的设置工作正常。

这是我的实现:

        val outputFolder = File(baseFolder + File.separator + "Output Folder")
        if (!outputFolder.exists()) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                Files.createDirectory(outputFolder.toPath()) //This allways returns false with targetSdkVersion 29
            } else {
                if (!outputFolder.mkdirs()) {
                    Log.e("SaveRaw", "Unable to create folder for audio recording")
                }
            }
        }

        outputFile = File("$baseFolder/Output Folder/$filename")
        try {
            fileOutputStream = FileOutputStream(outputFile)
        } catch (e: FileNotFoundException) {
            e.printStackTrace() // allways throwing exception here, even if Output Folder exists 
        }

这里是个例外:

W/System.err: java.io.FileNotFoundException: /storage/emulated/0/Chirp Auto Tester/2019_10_17 10:44:43.raw: open failed: EACCES (Permission denied)
W/System.err:     at libcore.io.IoBridge.open(IoBridge.java:496)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:235)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:186)

希望有人有答案,我在这里错过了什么?

更新:

这就是baseFolder 的来源。请注意,getExternalStorageDirectory 是一个已弃用的方法。

        val baseFolder: String = if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            Environment.getExternalStorageDirectory().absolutePath
        } else {
            context.filesDir.absolutePath
        }

谢谢

【问题讨论】:

  • 尝试使用这个库获得权限github.com/googlesamples/easypermissions
  • 正如我已经提到的,写入外部存储所需的权限被授予,并且在使用targetSdkVersion 28 构建时,相同的设置工作。这不是权限问题!
  • 其权限相邻,根据错误无法获得权限访问文件/storage/emulated/0/Chirp Auto Tester/2019_10_17 10:44:43.raw: open failed: EACCES (权限被拒绝),如果 android 10 使用范围存储
  • 那么你如何解释它在为targetSdkVersion 28 构建时有效?根据错误,是的,这是与权限相关的问题,但不是因为未授予权限。
  • 您没有文件系统访问外部和可移动存储上任意位置的权限。 removable storage 限制是在 Android 4.4 中添加的。 external storage limitation 是在 Android 10 中添加的。

标签: android kotlin filenotfoundexception android-external-storage android-api-levels


【解决方案1】:

Starting with Android 11 存储权限将被撤销,开发人员需要考虑通过SAFMedia Store 访问所需存储的替代方法。目前,您可以通过在应用程序标签内的清单中添加以下内容来继续使用您正在使用的内容:

android:requestLegacyExternalStorage="true"

您可能需要考虑将 minSDK 更改为 19 并使用 getExternalFilesDir() 来获取不需要任何权限的路径。

【讨论】:

  • 仍然无法正常工作,但肯定在寻找正确的方向,现在我收到了ENOENT (No such file or directory),但是,该目录肯定存在。 getExternalFilesDir() 听起来是解决问题的正确决定。
  • 只是一个快速更新,我暂时使用getExternalFilesDir()
【解决方案2】:

当您的应用以 Build.VERSION_CODES.Q 为目标时,从此方法返回的路径不再可供应用直接访问。

    val path = getExternalFilesDir(Environment.DIRECTORY_PICTURES.toString() + 
    File.separator + "Output Folder")
    val file = File(getExternalFilesDir(null), "filename.jpg")

更多详情请访问Docs

【讨论】:

    【解决方案3】:

    Android 版本 10(API 级别 29)引入了范围存储的概念。根据 Docs-“为了让用户更好地控制他们的文件并限制文件混乱,默认情况下,针对 Android 10(API 级别 29)及更高版本的应用程序被授予对外部存储或范围存储的范围访问权限。此类应用程序具有访问权限仅限于外部存储上的应用特定目录,以及应用创建的特定类型的媒体。”

    对于 Android 版本 10,您可以选择退出范围存储,方法是在应用程序标签内的 AndroidManifest.xml 中添加 android:requestLegacyExternalStorage="true"

    这是一个临时解决方案,仅适用于 android 版本 10,不适用于更高版本。还要确保你请求运行时读取/写入外部存储的权限。

    【讨论】:

      【解决方案4】:

      Android 11 将简单地忽略 android:requestLegacyExternalStorage="true",因为它是 Android

       <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
      <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
      

      以及在清单文件的应用程序标签中:

       android:requestLegacyExternalStorage="true"
      

      在清单文件的应用程序标签中也声明提供者,如下所示:

       <provider
              android:name="androidx.core.content.FileProvider"
              android:authorities="${applicationId}.provider"
              android:exported="false"
              android:grantUriPermissions="true">
              <meta-data
                  android:name="android.support.FILE_PROVIDER_PATHS"
                  android:resource="@xml/provider_path" />
          </provider>
      

      在 res 文件夹中创建一个文件夹名称 xml

      现在在您刚刚创建的 xml 文件夹中创建一个名为 provider_path.xml 的 xml 文件,并将以下代码粘贴到 xml 文件中:

      <?xml version="1.0" encoding="utf-8"?>
      
      <path>
      <external-path
          name="external_files"
          path="." />
      

      现在在你的活动中:

      File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+"/"+"sample.pdf");
                          if(file.exists()){
                              Uri uri = FileProvider.getUriForFile(context, "com.example.www"+".provider",file);
                              Intent i = new Intent(Intent.ACTION_VIEW);
                              i.setDataAndType(uri, "application/pdf");
                              i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_GRANT_READ_URI_PERMISSION);
                              context.startActivity(i);
                          }
      

      将 com.example.www 替换为您的应用程序包名称。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-06-03
        • 1970-01-01
        • 1970-01-01
        • 2015-08-28
        • 1970-01-01
        • 2014-05-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多