java - File.exists()对于实际存在的文件(目录)返回false

标签 java android file

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次。
  • 我已在 list 中声明了存储/读取外部存储/写入外部存储的权限,以要求在运行时提供权限。问题不在于此。 (如果有的话,我现在有太多的权限:D)。毕竟,如果这是一个权限问题,它每次都会失败,但就我而言,失败率是4%或5%。
  • 使用上面的代码,我试图创建一个指向“文档”文件夹的文件。在我的应用中,我实际上正在使用String myPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getPath();在发生错误的特定设备中,此路径恰好是“/storage/emulated/0/Documents”和,这就是为什么我在我给您的示例代码中将其硬编码为的原因。
  • 如果我在设备上使用文件浏览器应用程序(即“Astro文件管理器”),则可以看到该文件夹​​确实存在并具有一些内容,并且还确认真正的路径为“/storage/emulated/0/文件”。
  • 这在本地从未发生过。只有该应用程序的用户会遇到该问题,而由于Firebase/Crashlytics,我知道该问题存在。用户的平板电脑与我正在开发的平板电脑完全相同,即Lenovo TB-8504X。 (我在一家公司工作,我们同时提供软件和硬件)。

  • 那么,您对为什么发生此问题有任何想法吗?

    有没有人经历过类似的事情?

    有时,“文档”文件夹的路径是“/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.


  • 除非您使用NFS,否则该错误报告并不直接相关。
  • 这不是一个错误/错误。这是一个限制。
  • 在文件系统级别,Linux支持许多不同类型的文件系统是不争的事实,而且与“普通”文件系统相比,它们中许多都表现出意想不到的方式。 JVM不可能在Java API级别隐藏所有特定于文件系统的怪异边缘情况。
  • 在API级别上,File.exists无法报告任何错误。签名不允许它抛出IOException,并且抛出未经检查的异常将是一个重大更改。它只能说truefalse
  • 如果要区分生成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测试是否存在
    文件。
  • 错误时(模式下至少一位)
    要求提供被拒绝的许可,或者模式为F_OK且文件
    不存在,或发生其他错误),则返回-1,并且
    errno设置适当。
  • 如果出现以下情况,
  • access()将失败:
  • EACCES所请求的访问将被拒绝,或者按照以下条件搜索:
    路径前缀中的目录之一被拒绝执行任务
    的路径名。 (另请参见path_resolution(7)。)
  • ELOOP解析路径名时遇到太多符号链接(symbolic link)。
  • ENAMETOOLONG
    路径名太长。
  • ENOENT路径名的组件不存在或者是悬挂的符号
    关联。
  • ENOTDIR
    实际上,在路径名中用作目录的组件不是
    目录。
  • EROFS已请求对只读文件的写权限
    文件系统。
  • 如果出现以下情况,
  • access()可能会失败:
  • EFAULT路径名指向您可访问的地址空间之外。
  • EINVAL模式指定不正确。
  • EIO发生I/O错误。
  • ENOMEM可用的内核内存不足。
  • ETXTBSY
    已请求对正在执行的可执行文件进行写访问。

  • 我已经消除了我认为在技术上是不可能或不可能的错误,但是仍然有很少的问题需要考虑。

    另一种可能性是(例如,应用程序的其他部分)正在删除或重命名文件或(假想的)符号链接(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/

    相关文章:

    android - 错误膨胀类 ImageButton 停止应用程序启动

    java - 无法添加依赖 : Failed to resolve: androidx. 生命周期 :lifecycle-extensions:2. 2.0-rc2

    Java : Convert Jackson (JSON) class to Entity (JPA) Class

    java - Playframework 在动态 route 给出错误

    java - 有点奇怪的 JComboBox 行为

    Android:应用程序退出后出现奇怪的 NameNotFoundException

    django - Windows 上 Django 1.5 静态文件的正确配置是什么?

    file - 如何从 Blazor 服务器端下载内存文件

    file - Linux - client_body_in_file_only - 如何为临时文件设置文件权限?

    java - 说关键字 "private"在类级别是私有(private)的是什么意思?