【发布时间】:2019-12-30 11:29:06
【问题描述】:
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 次。
- 我已经在我的清单中声明了存储/读取外部存储/写入外部存储的权限并在运行时请求了权限。问题不在于那里。 (如果有什么我在这一点上有太多的权限:D)。毕竟,如果是权限问题,它每次都会失败,但在我的情况下,失败率是 4% 或 5%。
- 使用上面的代码,我试图创建一个指向“文档”文件夹的文件。在我的应用程序中,我实际上使用的是
String myPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getPath();
在发生错误的特定设备中,此路径恰好是“/storage/emulated/0/Documents”,这就是我在给你的示例代码中硬编码它的原因。 - 如果我在设备上使用文件资源管理器应用程序(即“Astro 文件管理器”,我可以看到该文件夹确实存在并且有一些内容,并且还确认路径真的 是“/storage/emulated/0/Documents”。
- 这在我本地从未发生过。只有应用程序的用户会遇到此问题,并且由于 Firebase/Crashlytics,我知道问题的存在。用户拥有与我用于开发的完全相同的平板电脑,即联想 TB-8504X。 (我在一家公司工作,我们提供软件和硬件)。
那么,您对为什么会出现这个问题有任何想法吗?
有没有人经历过类似的事情?
“文档”文件夹的路径是否有时会是“/storage/emulated/0/Documents”,有时会变成在同一物理设备上的其他路径吗?
我是一位经验丰富的 Android 开发人员,但我在 Android 架构和 Android 文件系统方面相当新手。可能是在启动时(当设备通电或重新启动后)文件系统在我的代码检查目录是否存在时尚未“安装”“磁盘”?在这里,我尽可能松散地使用术语“安装”和“磁盘”。此外,我的应用程序实际上是一个启动器/家长控制应用程序,因此它是设备启动时首先触发的东西。我几乎确信这根本没有意义,但在这一点上,我正试图看到更大的图景并探索超越典型 Android 开发的解决方案。
非常感谢您的帮助,因为这个问题开始让我感到不安。
期待任何有用的回复。
提前致谢。
编辑(2019 年 8 月 27 日):
我遇到了这个Java Bug Report,虽然它已经过时了。据此,在 NFS 挂载的卷上操作时,java.io.File.exists 最终会执行stat(2)。如果stat 失败(它可能由于多种原因而失败),那么File.exists(错误地)假定stat'ed 的文件不存在。难道这就是我烦恼的根源吗?
编辑(2019 年 8 月 28 日):
今天我可以为这个问题添加一个赏金,试图引起更多的关注。我会鼓励您仔细阅读问题,查看 cmets 忽略声称这与 Realm 的客户支持有关的问题。领域代码确实是使用不可靠方法的代码,但我想知道是该方法不可靠的原因。 Realm 是否可以解决此问题并改用其他代码,这超出了问题的范围。我只是想知道是否可以安全地使用File.exists(),如果不能,为什么?
再次,提前谢谢大家。获得答案对我来说非常重要,即使它过于技术性并且涉及对 NFS 文件系统、Java、Android、Linux 或其他任何东西的更深入了解!
编辑(2019 年 8 月 30 日):
因为一些用户建议用其他方法替换File.exists(),我想说明我现在感兴趣的是低估了为什么该方法失败了而不是 可以改用什么作为解决方法。
即使我想用其他东西替换File.exists(),我也无法这样做,因为这段代码位于RealmConfiguration.java 文件中(阅读-only) 这是我在我的应用程序中使用的领域库的一部分。
为了让事情更清楚,我将提供两段代码。我在活动中使用的代码以及在 RealmConfiguration.java 中调用的方法因此:
我在活动中使用的代码:
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 存在于我的活动中,调用了 Realm 代码,突然 myFile 不再存在。我再次指出,这是不一致的。我注意到崩溃率为 4-5%,这意味着 myFile 大部分时间都存在于活动中以及领域代码对其进行检查时。
我希望这会有所帮助。
再次提前致谢!
【问题讨论】:
-
我将完全删除
exists()测试并留下mkdirs()通话。并忽略它是成功还是失败。然后,如果和何时失败,打开文件的实际尝试会给您一个异常并带有有意义的错误消息。在你得到它并在这里发布之前,我们都只是猜测。 -
我投票决定将此问题作为题外话结束,因为该解决方案需要产品支持,而不是计算机编程。
-
为了说服领域开发团队用其他东西替换
exists(),我必须首先确认这是问题所在。我的问题与exists()行为有关,这肯定是关于计算机编程,尤其是Java。如果exists()不可靠,则应更新 Java 以解决此问题,或者至少提及此不可靠行为,以便开发人员避免使用它或谨慎使用它,包括 Realm 开发团队。但再一次,我不能 100% 确定这是问题的根源。也许我错过了其他东西。因此我提出了一个问题...... -
首先,我不会从任何地方删除
exists;该方法的存在是有原因的。其次,这是一个有效的编程问题,与“联系供应商”无关。 The Java SDK for Realm is open source,因此您实际上可以查看它并根据需要分叉它(专有的是数据库本身)。 OP 提出了合理的担忧。我会向 RealmDB 的人指出这一点(他们经常阅读 SO,并且以回答问题而闻名,或者在 2015 年使用它时已经习惯了)。祝你好运。 -
至于为什么会出现这种行为,好吧,我不确定,但是 RealmDB 以在主线程上做很多事情而闻名,因为它很快,所以也许这是一种罕见的情况有时文件系统足够慢以至于您遇到这种奇怪行为的情况;这只是一个理论。我已经有几年没有接触过 realmdb 了;如果您查看您链接的文件,他们清楚地说:警告:此方法只是一个时间点检查。除非受到外部同步的保护,否则其他线程或进程可能在此方法返回后立即创建或删除了 Realm 文件。