TLDR:File.exists()
有错误,我想了解原因!
我在Android应用程序中遇到了一个奇怪的问题(经常发生)。我会尽量简短。
首先,我将向您显示代码,然后提供一些其他信息。这不是完整的代码。只是问题的核心。
示例代码:
String myPath = "/storage/emulated/0/Documents";
File directory= new File(myPath);
if (!directory.exists() && !directory.mkdirs()) {
throw new IllegalArgumentException("Could not create the specified directory: " + directory.getAbsolutePath() + ".");
}
大多数情况下,可以正常工作。 几次,但是引发了异常,这意味着该目录不存在和无法创建。每运行100次,它就可以在95-96次上正常运行,而失败4-5次。
String myPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getPath();
在发生错误的特定设备中,此路径恰好是“/storage/emulated/0/Documents”和,这就是为什么我在我给您的示例代码中将其硬编码为的原因。 那么,您对为什么发生此问题有任何想法吗?
有没有人经历过类似的事情?
有时,“文档”文件夹的路径是“/storage/emulated/0/Documents”,有时在同一物理设备上又变成了吗?
我是一位经验丰富的Android开发人员,但在Android体系结构和Android文件系统方面还是一个新手。可能是在启动时(设备开机或重新启动后)文件系统尚未在我的代码检查目录是否存在时“挂载”“磁盘”吗?在这里,我尽可能宽松地使用术语“安装”和“磁盘”。另外,我的应用程序实际上是启动器/家长控制应用程序,因此它是设备启动时首先触发的内容。我几乎确信这根本没有道理,但在这一点上,我试图查看更广阔的前景,并探索超越典型Android开发的解决方案。
在这个问题开始困扰我的时候,我将非常感谢您的帮助。
期待任何有用的回应。
提前致谢。
编辑(27/08/2019):
我碰到了这个Java Bug Report,尽管它已经过时了。据此,当在挂载NFS的卷上操作时,
java.io.File.exists
最终执行stat(2)
。如果stat
失败(可能由于多种原因而导致失败),则File.exists
(错误地)假定作为stat'ed
的文件不存在。这可能是我麻烦的根源吗?编辑(28/08/2019):
今天,我可以向这个问题添加赏金,以引起更多关注。我鼓励您仔细阅读问题,浏览的评论,而忽略的观点,该评论声称这与Realm的客户支持有关。领域代码确实是使用不可靠方法的代码,但是我想知道的是为什么该方法不可靠的原因。 Realm是否可以解决此问题并改用其他代码,这超出了问题的范围。我只是想知道是否可以安全地使用
File.exists()
,如果不能,则为什么使用?再次感谢大家。即使答案过于技术性并且涉及对NFS文件系统,Java,Android,Linux或任何其他内容的更深入的了解,对我来说也很重要。
编辑(30/08/2019):
因为有些用户建议用其他方法替换
File.exists()
,所以我想指出,我对此感兴趣的是低估了为什么该方法使失败,而不是不能用作替代方法。即使我想用其他东西代替
File.exists()
,我也无法这样做,因为这段代码驻留在RealmConfiguration.java
文件(只读)中,该文件是我在应用程序中使用的领域库的一部分。为了使事情更加清楚,我将提供两段代码。结果,我在 Activity 中使用的代码以及在
RealmConfiguration.java
中调用的方法:我在 Activity 中使用的代码:
File myfile = new File("/storage/emulated/0/Documents");
if(myFile.exists()){ //<---- Notice that myFile exists at this point.
Realm.init(this);
config = new RealmConfiguration.Builder()
.name(".TheDatabaseName")
.directory(myFile) //<---- Notice this line of code.
.schemaVersion(7)
.migration(new MyMigration())
.build();
Realm.setDefaultConfiguration(config);
realm = Realm.getDefaultInstance();
}
此时,存在myFile,并调用了
RealmConfiguration.java
中的代码。崩溃的
RealmConfiguration.java
方法: /**
* Specifies the directory where the Realm file will be saved. The default value is {@code context.getFilesDir()}.
* If the directory does not exist, it will be created.
*
* @param directory the directory to save the Realm file in. Directory must be writable.
* @throws IllegalArgumentException if {@code directory} is null, not writable or a file.
*/
public Builder directory(File directory) {
//noinspection ConstantConditions
if (directory == null) {
throw new IllegalArgumentException("Non-null 'dir' required.");
}
if (directory.isFile()) {
throw new IllegalArgumentException("'dir' is a file, not a directory: " + directory.getAbsolutePath() + ".");
}
------> if (!directory.exists() && !directory.mkdirs()) { //<---- Here is the problem
throw new IllegalArgumentException("Could not create the specified directory: " + directory.getAbsolutePath() + ".");
}
if (!directory.canWrite()) {
throw new IllegalArgumentException("Realm directory is not writable: " + directory.getAbsolutePath() + ".");
}
this.directory = directory;
return this;
}
因此,myFile存在于我的 Activity 中,调用了Realm代码,突然myFile不再存在。.我再次指出这是不一致的。我注意到崩溃的发生率为4-5%,这意味着myFile在大多数情况下都存在于 Activity 中,并且在领域代码进行检查时都存在。
我希望这会有所帮助。
再次预先感谢!
最佳答案
首先,如果您使用的是Android,则Java Bugs数据库中的错误报告不相关。 Android不使用Sun/Oracle代码库。 Android最初是对Java类库的无尘室重新实现。
因此,如果Android上的File.exists()
中存在错误,则这些错误将位于Android代码库中,并且所有报告都将位于Android问题跟踪器中。
但是当你这样说时:
According to this, when operating on NFS-mounted volumes, java.io.File.exists ends up performing a stat(2). If the stat fails (which it may do for several reasons), then
File.exists
(mistakenly) assumes that the file being stat'ed does not exist.
File.exists
无法报告任何错误。签名不允许它抛出IOException
,并且抛出未经检查的异常将是一个重大更改。它只能说true
或false
。 false
的各种原因,则应改用较新的Files.exists(Path, LinkOptions...)
方法。 Could this be the source of my troubles?
是的,它可以,而不仅仅是在NFS情况下!见下文。 (使用
Files.exist
时,NFS stat
失败很可能是EIO
,这将引发IOException
而不是返回false
。)Android代码库(版本android-4.2.2_r1)中的File.java代码为:
public boolean exists() {
return doAccess(F_OK);
}
private boolean doAccess(int mode) {
try {
return Libcore.os.access(path, mode);
} catch (ErrnoException errnoException) {
return false;
}
}
注意如何将任何
ErrnoException
转换为false
。进一步的挖掘表明os.access调用正在执行 native 调用,该调用将进行
access
系统调用,如果系统调用失败,则将引发ErrnoException
。因此,现在我们需要查看访问系统调用的已记录行为。这是
man 2 access
所说的:文件。
要求提供被拒绝的许可,或者模式为F_OK且文件
不存在,或发生其他错误),则返回-1,并且
errno设置适当。
路径前缀中的目录之一被拒绝执行任务
的路径名。 (另请参见path_resolution(7)。)
路径名太长。
关联。
实际上,在路径名中用作目录的组件不是
目录。
文件系统。
已请求对正在执行的可执行文件进行写访问。
我已经消除了我认为在技术上是不可能或不可能的错误,但是仍然有很少的问题需要考虑。
另一种可能性是(例如,应用程序的其他部分)正在删除或重命名文件或(假想的)符号链接(symbolic link),或者更改了文件权限...。
但是我不认为
File.exist()
损坏了,或者主机操作系统没有损坏。从理论上讲这是可能的,但是您需要一些明确的证据来支持该理论。1-从不表现出与方法的已知行为不同的意义上讲,它没有被破坏。您可能会争论不休,直到人们意识到行为是否“正确”为止,但是从Java 1.0开始就一直如此,并且不能在OpenJDK或Android中更改它,而不会破坏过去20多年来编写的成千上万个现有应用程序年。不会发生的
接下来做什么?
好吧,我的建议是使用
strace
跟踪您的应用程序正在执行的系统调用,并查看您是否可以从中获得一些线索,以了解为什么某些access
系统调用会给您带来意想不到的结果。例如什么是路径,什么是errno
。参见https://source.android.com/devices/tech/debug/strace。
关于java - File.exists()对于实际存在的文件(目录)返回false,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57655086/