为什么我没有权限在外部存储上写入app dir?

时间:2016-10-26 02:01:57

标签: android file-permissions android-sdcard android-external-storage

TL; DR问题摘要:我的Android应用尝试到SD卡上应用的外部存储目录。它因权限错误而失败。但是,提取到最小测试应用程序中的相同代码(方法)会成功!

由于我们的目标API级别包括KitKat及更高版本(以及JellyBean),并且KitKat限制应用程序在SD卡上的任何位置写入,但应用程序指定的外部存储目录除外,该应用程序尝试写入指定的目录{{ 1}}。我通过从/path/to/sdcard/Android/data/com.example.myapp/files获取目录列表并找到Activity.getExternalFilesDirs(null);的目录来验证此目录的路径。即我们不是硬编码SD卡的路径,因为它因制造商和设备而异。以下是演示此问题的代码:

isRemovable()

checkDir()方法不相关,但我会在此处将其包含在内以保证完整性。它只是确保目录位于已挂载的可移动存储上,并记录目录的其他属性(存在,可写)。

// Attempt to create a test file in dir.
private void testCreateFile(File dir) {
    Log.d(TAG, ">> Testing dir " + dir.getAbsolutePath());

    if (!checkDir(dir)) { return; }

    // Now actually try to create a file in this dir.
    File f = new File(dir, "foo.txt");
    try {
        boolean result = f.createNewFile();
        Log.d(TAG, String.format("Attempted to create file. No errors. Result: %b. Now exists: %b",
                result, f.exists()));
    } catch (Exception e) {
        Log.e(TAG, "Failed to create file " + f.getAbsolutePath(), e);
    }
}

在测试应用程序(在Android 5.1.1上运行)中,以下日志输出显示代码正常运行:

private boolean checkDir(File dir) {
    boolean isRemovable = false;
    // Can't tell whether it's removable storage?
    boolean cantTell = false;
    String storageState = null;

    // Is this the primary external storage directory?
    boolean isPrimary = false;
    try {
        isPrimary = dir.getCanonicalPath()
                .startsWith(Environment.getExternalStorageDirectory().getCanonicalPath());
    } catch (IOException e) {
        isPrimary = dir.getAbsolutePath()
                .startsWith(Environment.getExternalStorageDirectory().getAbsolutePath());
    }

    if (isPrimary) {
        isRemovable = Environment.isExternalStorageRemovable();
        storageState = Environment.getExternalStorageState();
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        // I actually use a try/catch for IllegalArgumentException here, but 
        // that doesn't affect this example.
        isRemovable = Environment.isExternalStorageRemovable(dir);
        storageState = Environment.getExternalStorageState(dir);
    } else {
        cantTell = true;
    }

    if (cantTell) {
        Log.d(TAG, String.format("  exists: %b  readable: %b  writeable: %b primary: %b  cantTell: %b",
                dir.exists(), dir.canRead(), dir.canWrite(), isPrimary, cantTell));
    } else {
        Log.d(TAG, String.format("  exists: %b  readable: %b  writeable: %b primary: %b  removable: %b  state: %s  cantTell: %b",
                dir.exists(), dir.canRead(), dir.canWrite(), isPrimary, isRemovable, storageState, cantTell));
    }

    return (cantTell || (isRemovable && storageState.equalsIgnoreCase(MEDIA_MOUNTED)));
}

因此文件已成功创建。但在我的实际应用程序中(也在Android 5.1.1上运行),对10-25 19:56:40 D/MainActivity: >> Testing dir /storage/extSdCard/Android/data/com.example.testapp/files 10-25 19:56:40 D/MainActivity: exists: true readable: true writeable: true primary: false removable: true state: mounted cantTell: false 10-25 19:56:40 D/MainActivity: Attempted to create file. No errors. Result: false. Now exists: true 的调用因权限错误而失败:

createNewFile()

在将此标记为重复之前:我已经阅读了有关SO的其他几个问题,这些问题描述了在KitKat或更高版本下写入SD卡时的权限失败。但是没有一个原因或解决方案似乎适用于这种情况:

  • 设备连接为大容量存储。我仔细检查了一下。但是,MTP已开启。 (如果不拔掉用于查看日志的USB线,我无法关闭它。)
  • 我的清单包括10-25 18:14:56... D/LessonsDB: >> Testing dir /storage/extSdCard/Android/data/com.example.myapp/files 10-25 18:14:56... D/LessonsDB: exists: true readable: true writeable: true primary: false removable: true state: mounted cantTell: false 10-25 18:14:56... E/LessonsDB: Failed to create file /storage/extSdCard/Android/data/com.example.myapp/files/foo.txt java.io.IOException: open failed: EACCES (Permission denied) at java.io.File.createNewFile(File.java:941) at com.example.myapp.dmm.LessonsDB.testCreateFile(LessonsDB.java:169) ... Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied) at libcore.io.Posix.open(Native Method) at libcore.io.BlockGuardOs.open(BlockGuardOs.java:186) at java.io.File.createNewFile(File.java:934) ...
  • 我的目标是API级别22,因此我不应该在运行时 strong 请求权限。 build.gradle有<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />(以及targetSdkVersion 22buildToolsVersion '21.1.2')。
  • 我正在使用KitKat和Lollipop;我甚至没有棉花糖装置。所以,我不应该在运行时请求权限,即使我的目标是API级别23。
  • 如上所述,我正在写入指定的外部存储目录,这应该是我的应用程序可写的,即使在KitKat上也是如此。
  • 外部存储已安装;代码验证了这一点。

何时起作用以及何时起作用的摘要:

  • 在我的pre-KitKat设备上,测试应用程序和真实应用程序都可以正常工作。他们在SD卡上的app dir中成功创建了一个文件。
  • 在我的KitKat和Lollipop设备上,测试应用程序工作正常,但真正的应用程序没有。相反,它会在上面的日志中显示错误。

测试应用和真实应用之间有什么区别?嗯,显然真正的应用程序中有更多的东西。但我看不出任何重要的事情。两者都具有相同的compileSdkVersion 23compileSdkVersiontargetSdkVersion等。两者都使用buildToolsVersion作为依赖。

2 个答案:

答案 0 :(得分:8)

由于一个应用程序正常运行而另一个应用程序没有用,因此应用程序之间存在差异,而不是设备或卡。相关目录不需要任何Android权限(例如WRITE_EXTERNAL_STORAGE)。如果Android系统没有正确设置文件系统权限,那么你无法写入它的唯一原因就是它。

  

我可能自己创建了该目录,但未能在某处设置权限?

我不确定您是否可以从应用程序外部创建该目录并使其正常工作。理想情况下,这样会很好,但我没有尝试过,我可以看到可能造成问题的地方。

  

还有其他理由不相信吗?

鉴于正在进行的文件系统恶作剧,当开发人员对路径的性质做出假设时,我会非常紧张。

答案 1 :(得分:2)

我已经对这个问题有了更多的了解,与CommonsWare的答案有很大的不同,我觉得它值得一个新的答案。

  • 对我原来的情况有所不同的是SD卡:如果卡已经一个/Android/data/com.example.myapp文件夹,而未在此手机上创建,然后应用程序可能无法获得写入该文件夹的权限。如果文件夹不存在,应用程序可以创建它,并写入它。至少在KitKat和更高版本中。
    • this article中解释的内容支持此功能:
        

      ...从API级别19 [KitKat]开始,不再需要READ_EXTERNAL_STORAGE来访问位于外部存储上的文件 - 前提是由FUSE守护程序创建的数据文件夹与应用程序的包名称匹配。安装应用程序时,FUSE将在外部存储上处理合成文件的所有者,组和模式 [强调添加]。

    • 因此,这证实了问题出现的猜测,因为我手动创建了应用程序的数据文件夹,而不是让Android根据需要进行设置。未来的研究:不仅仅查看像WRITE_EXTERNAL_STORAGE这样的应用程序权限,还要检查文件系统上应用程序数据文件夹的用户,组和模式设置:手动创建和应用程序安装创建时。比较&amp;对比!也许这将提供足够的信息,以允许手动创建应用程序的数据文件夹并仍然有效。请记住,SD卡的FAT32文件系统上有一个包装器/仿真层,因此我们需要考虑两个文件系统层。
  • 在稍后的情况下,我发现应用程序需要调用context.getExternalFilesDirs(null)才能在SD卡上创建/Android/data/com.example.myapp文件夹。 (至少在Android 5.1 Lollipop及更高版本上。需要在KitKat上进行测试。)