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 раз.
- Я объявил разрешения для хранения/чтения внешнего хранилища/записи внешнего хранилища в моем манифесте и asked for the permissions on runtime. The problem does not lie there. (If anything i have too many permissions at this point :D). After all, if it was a permission issue it would fail every time but in my case it fails at a rate of 4% or 5%.
- запросил разрешения во время выполнения. Проблема не в этом. (Во всяком случае, у меня слишком много разрешений на данный момент: D). В конце концов, если бы это была проблема с разрешением, она бы терпела неудачу каждый раз, но в моем случае она терпела неудачу со скоростью 4% или 5%.С помощью приведенного выше кода я пытаюсь создать файл, который указывает на папку "Документы". В моем приложении я на самом деле использую
String myPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getPath();
В конкретном устройстве, где возникает ошибка, этот путь называется "/storage/emulated/0/Documents" и , поэтому я жестко закодировал его в приведенном мной примере кода. - Если я использую на устройстве приложение для просмотра файлов (например, "Astro file manager"), я вижу, что папка существует и имеет некоторое содержимое, а также подтверждаю, что путь действительно равен "/storage/emulated/0/Documents".
- Это никогда не случалось со мной на месте. Проблема возникает только у пользователей приложения, и я знаю, что проблема существует благодаря 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):
Сегодня я могу добавить награду bounty к этому вопросу, пытаясь привлечь больше внимания. Я бы посоветовал вам внимательно прочитать вопрос, просмотреть комментарии , игнорируя тот, который утверждает, что это связано с поддержкой клиентов из Realm. Код области действительно используется ненадежным методом, но я хочу знать, поэтому метод ненадежен. Вопрос о том, может ли Realm обойти это и использовать какой-то другой код, выходит за рамки вопроса. Я просто хочу знать, можно ли безопасно использовать File.exists(), а если нет, , почему?
Еще раз спасибо всем заранее. Для меня было бы очень важно получить ответ, даже если он слишком технический и предполагает более глубокое понимание файловых систем NFS, Java, Android, Linux или чего-либо еще!
ОБНОВЛЕНИЕ (30/08/2019):
Поскольку некоторые пользователи предлагают заменить File.exists() каким-либо другим методом, я хотел бы заявить, что то, что меня интересует в данный момент, подчеркивает, почему метод терпит неудачу , а не какой можно использовать вместо этого в качестве обходного пути.
Даже если бы я захотел заменить File.exists() чем-то другим, я не смогу сделать это, потому что этот фрагмент кода находится в файле RealmConfiguration.java (только для чтения), который является частью Библиотека областей, которую я использую в своем приложении.
Чтобы сделать вещи еще яснее, я приведу два куска кода. Код, который я использую в своей деятельности, и метод, который вызывается в 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 большую часть времени существует как в действии, так и когда код области проверяет его.
Я надеюсь, что это будет полезно.
Еще раз спасибо заранее!