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卡时的权限失败。但是没有一个原因或解决方案似乎适用于这种情况:
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)
...
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
(以及targetSdkVersion 22
,buildToolsVersion '21.1.2'
)。何时起作用以及何时起作用的摘要:
测试应用和真实应用之间有什么区别?嗯,显然真正的应用程序中有更多的东西。但我看不出任何重要的事情。两者都具有相同的compileSdkVersion 23
,compileSdkVersion
,targetSdkVersion
等。两者都使用buildToolsVersion
作为依赖。
答案 0 :(得分:8)
由于一个应用程序正常运行而另一个应用程序没有用,因此应用程序之间存在差异,而不是设备或卡。相关目录不需要任何Android权限(例如WRITE_EXTERNAL_STORAGE
)。如果Android系统没有正确设置文件系统权限,那么你无法写入它的唯一原因就是它。
我可能自己创建了该目录,但未能在某处设置权限?
我不确定您是否可以从应用程序外部创建该目录并使其正常工作。理想情况下,这样会很好,但我没有尝试过,我可以看到可能造成问题的地方。
还有其他理由不相信吗?
鉴于正在进行的文件系统恶作剧,当开发人员对路径的性质做出假设时,我会非常紧张。
答案 1 :(得分:2)
我已经对这个问题有了更多的了解,与CommonsWare的答案有很大的不同,我觉得它值得一个新的答案。
/Android/data/com.example.myapp
文件夹,而未在此手机上创建,然后应用程序可能无法获得写入该文件夹的权限。如果文件夹不存在,应用程序可以创建它,并写入它。至少在KitKat和更高版本中。
...从API级别19 [KitKat]开始,不再需要READ_EXTERNAL_STORAGE来访问位于外部存储上的文件 - 前提是由FUSE守护程序创建的数据文件夹与应用程序的包名称匹配。安装应用程序时,FUSE将在外部存储上处理合成文件的所有者,组和模式 [强调添加]。
context.getExternalFilesDirs(null)
才能在SD卡上创建/Android/data/com.example.myapp
文件夹。 (至少在Android 5.1 Lollipop及更高版本上。需要在KitKat上进行测试。)