【发布时间】:2017-03-08 05:52:47
【问题描述】:
TL;DR 问题摘要:我的 Android 应用尝试写入到应用在 SD 卡上的外部存储目录。它因权限错误而失败。但是同样的代码(方法),提取到一个最小的测试应用程序中,成功了!
由于我们的目标 API 级别包括 KitKat 及更高版本(以及 JellyBean),并且 KitKat 限制应用程序写入 SD 卡上除应用程序指定的外部存储目录之外的任何位置,因此应用程序尝试写入该指定目录,@987654321 @。我通过从Activity.getExternalFilesDirs(null); 获取目录列表并找到isRemovable() 的目录来验证此目录的路径。 IE。我们没有硬编码 SD 卡的路径,因为它因制造商和设备而异。这是演示问题的代码:
// 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() 方法不那么相关,但为了完整起见,我将在此处包含它。它只是确保目录位于已挂载的可移动存储上,并记录目录的其他属性(存在、可写)。
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
所以文件创建成功。但在我的实际应用程序中(也在 Android 5.1.1 上运行),对 createNewFile() 的调用失败并出现权限错误:
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 卡时权限失败。但给出的原因或解决方案似乎都不适用于这种情况:
- 设备未连接为大容量存储。我仔细检查了一遍。但是,MTP 已开启。 (如果不拔掉用于查看日志的 USB 电缆,我就无法将其关闭。)
- 我的清单包括
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - 我的目标是 API 级别 22,因此我不必在运行时请求权限(例如 Marshmallow)。 build.gradle 有
targetSdkVersion 22(和buildToolsVersion '21.1.2'、compileSdkVersion 23)。 - 我在 KitKat 和 Lollipop 上运行;我什至没有棉花糖设备。同样,我不应该在运行时请求权限,即使我的目标是 API 级别 23。
- 如上所述,我正在写入指定的外部存储目录,我的应用应该可以写入该目录,即使在 KitKat 上也是如此。
- 外部存储已安装;代码验证了这一点。
什么时候有效,什么时候无效的总结:
- 在我之前的 KitKat 设备上,测试应用程序和真实应用程序都可以正常工作。他们成功地在 SD 卡上的应用程序目录中创建了一个文件。
- 在我的 KitKat 和 Lollipop 设备上,测试应用程序运行良好,但真正的应用程序不能。相反,它会在上述日志中显示错误。
测试应用和真实应用有什么区别?好吧,显然真正的应用程序中有更多的东西。但我看不到任何重要的东西。两者都有相同的compileSdkVersion、targetSdkVersion、buildToolsVersion 等。两者都使用compile 'com.android.support:appcompat-v7:23.4.0' 作为依赖项。
【问题讨论】:
-
就您的问题而言,请完全卸载失败的应用程序(例如,
adb uninstall,或从“设置”中删除)。确保此目录已从可移动存储中删除。再次安装并运行您的应用,看看它现在是否表现得更好。 -
@CommonsWare:看起来卸载应用程序并确保删除了目录就可以了!我可能自己创建了该目录,但未能在某处设置权限??谢谢!如果您创建答案,我将有地方奖励赏金。但是弄清楚将来如何避免这个问题会很有帮助......能够在安装应用程序之前创建目录会很有帮助,而且我不想在这样做时弄乱权限。
-
@CommonsWare:“
getExternalFilesDirs()不会在 Android 4.4 之前返回单独的可移动存储卷” - 从我正在查看的文档中,getExternalFilesDirs()在 Android 4.4 之前不可用(API 19 级)。所以我不确定你的意思。 “在 Android 4.4 之前,大多数应用程序应该直接忽略可移动存储。”我不怀疑你是对的。不幸的是,这对我们来说不是一个选择……我们的应用程序都是关于可移动存储的,而且可能超过一半的用户是 4.4 之前的版本。附言感谢您调查此问题。 -
@LarsH:“所以我不确定你的意思”——对不起,我在想
ActivityCompat,它有一个向后兼容的getExternalFilesDirs(),它返回一个元素列表一直在 4.3 及以上版本。 -
@LarsH:正确。这种情况几乎就是他们添加
android:maxSdkVersion的原因。
标签: android file-permissions android-sdcard android-external-storage