为什么我没有写外部存储上的应用程序目录的权限? [英] Why don't I have permission to write to app dir on external storage?

查看:168
本文介绍了为什么我没有写外部存储上的应用程序目录的权限?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

The TL;DR question summary: My Android app tries to write to the app's external storage directory on an SD card. It fails with a permissions error. But the same code (method), extracted into a minimal test app, succeeds!

由于我们的目标API级别包括KitKat和更高版本(以及JellyBean),并且KitKat限制了应用程序只能在SD卡上的任何位置(应用程序的指定外部存储目录除外)进行写操作,因此该应用程序尝试写入该指定目录.我通过从Activity.getExternalFilesDirs(null);获取目录列表并找到该目录isRemovable()来验证此目录的路径. IE.我们不会对SD卡的路径进行硬编码,因为SD卡的路径会因制造商和设备而异.这是演示问题的代码:

Since our target API level includes KitKat and later (as well as JellyBean), and KitKat restricts apps from writing anywhere on the SD card except the app's designated external storage directory, the app tries to write to that designated directory, /path/to/sdcard/Android/data/com.example.myapp/files. I verify this directory's path by getting a list of directories from Activity.getExternalFilesDirs(null); and finding one that isRemovable(). I.e. we're not hard-coding the path to the SD card, because it varies by manufacturer and device. Here is code that demonstrates the problem:

// 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);
    }
}

checkDir()方法不那么相关,但出于完整性考虑,我将其包括在这里.只需确保该目录位于已安装的可移动存储上,并记录该目录的其他属性(存在,可写)即可.

The checkDir() method is not as relevant, but I'll include it here for completeness. It just makes sure the directory is on removable storage that is mounted, and logs other properties of the directory (exists, writable).

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上运行)中,以下日志输出显示该代码运行正常:

In the test app (running on Android 5.1.1), the following log output shows that the code is working fine:

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

因此文件已成功创建.但是在我的实际应用程序(也在Android 5.1.1上运行)中,对createNewFile()的调用失败,并出现权限错误:

So the file was created successfully. But in my actual app (also running on Android 5.1.1), the call to createNewFile() fails with a permissions error:

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)
    ...

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

Before you mark this as a duplicate: I have read through several other questions on SO describing permission failures when writing to an SD card under KitKat or later. But none of the causes or solutions given seem to apply to this situation:

  • 将设备连接为大容量存储.我仔细检查了.但是,MTP已打开. (除非拔下用于查看日志的USB电缆,否则我无法将其关闭.)
  • 我的清单包括<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  • 我的目标是API级别22,因此我不必在运行时请求许可(例如棉花糖). build.gradle具有targetSdkVersion 22(和buildToolsVersion '21.1.2'compileSdkVersion 23).
  • 我正在使用KitKat和Lollipop;我什至没有棉花糖装置.再次重申,即使我的目标是API级别23,也不必在运行时请求权限.
  • 如上所述,我正在写入指定的外部存储目录,该目录应该可由我的应用程序写入,即使在KitKat上也是如此.
  • 已安装外部存储;代码验证了这一点.
  • The device is not connected as mass storage. I double-checked. However, MTP is on. (I can't turn it off without unplugging the USB cable that I use for viewing the logs.)
  • My manifest includes <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  • I'm targeting API level 22, so I shouldn't have to request permission at run-time (a la Marshmallow). The build.gradle has targetSdkVersion 22 (and buildToolsVersion '21.1.2', compileSdkVersion 23).
  • I'm running on KitKat and Lollipop; I don't even have a Marshmallow device. So again, I shouldn't have to request permission at runtime, even if I were targeting API level 23.
  • As mentioned above, I'm writing to the designated external storage directory that's supposed to be writeable by my app, even on KitKat.
  • The external storage is mounted; the code verifies this.

何时工作以及何时不工作的摘要:

Summary of when it works and when it doesn't:

  • 在我的KitKat之前的设备上,测试应用程序和真实应用程序都可以正常工作.他们成功地在SD卡上的应用程序目录中创建了文件.
  • 在我的KitKat和Lollipop设备上,测试应用程序可以正常运行,但真正的应用程序却不能.而是在上面的日志中显示错误.

测试应用程序与真实应用程序之间有什么区别?好吧,显然,真正的应用程序中包含很多东西.但是我看不到任何重要的东西.两者都有相同的compileSdkVersiontargetSdkVersionbuildToolsVersion等.它们都还使用compile 'com.android.support:appcompat-v7:23.4.0'作为依赖项.

What difference is there between the test app and the real app? Well, obviously the real app has a lot more stuff in it. But I can't see anything that should matter. Both have identical compileSdkVersion, targetSdkVersion, buildToolsVersion, etc. Both are also using compile 'com.android.support:appcompat-v7:23.4.0' as a dependency.

推荐答案

我已经了解了更多有关此问题的知识,它与CommonsWare的答案有足够的不同,我认为这值得一个新的答案.

I've learned more about this issue, and it's different enough from CommonsWare's answer that I think it's worth a new answer.

  • 与我最初的情况不同的是SD卡:如果卡已经在其上没有在手机上创建的/Android/data/com.example.myapp文件夹,则该应用可能无法获得写入该文件夹的权限.而如果该文件夹不存在,则应用程序可以创建该文件夹并对其进行写入.至少在KitKat和更高版本中.
    • What made the difference for my original scenario was the SD cards: If a card already had a /Android/data/com.example.myapp folder on it that was not created on this phone, then the app might have trouble getting permission to write to that folder. Whereas if the folder did not exist, the app could create it, and write to it. At least in KitKat and later.
      • This is supported by something explained in this article:

      ...从API级别19 [KitKat]开始,只要由FUSE守护程序创建的 data文件夹与应用程序的软件包名称匹配,就不再需要READ_EXTERNAL_STORAGE来访问位于外部存储器上的文件. FUSE将在安装应用程序时综合外部存储中文件的所有者,组和模式 .

      ... starting with API Level 19 [KitKat], READ_EXTERNAL_STORAGE was no longer required to access files located on external storage – provided the data folder created by the FUSE daemon matches the app’s package name. FUSE would handle synthesizing the owner, group, and modes of files on external storage when an application is installed [emphasis added].

    • 因此,这证实了出现问题的猜测,因为我手动创建了应用程序的数据文件夹,而不是让Android根据需要进行设置.未来的研究:不仅要查看诸如WRITE_EXTERNAL_STORAGE之类的应用程序权限,还要检查文件系统上应用程序数据文件夹的用户,组和模式设置:手动创建和通过应用程序安装创建时.比较和对比!也许这将提供足够的信息,以允许手动创建该应用程序的数据文件夹,并且该文件夹仍然可以工作.请记住,SD卡的FAT32文件系统上有一个包装器/仿真层,因此我们需要同时考虑这两个文件系统层.
    • 这篇关于为什么我没有写外部存储上的应用程序目录的权限?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆