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 большую часть времени существует как в действии, так и когда код области проверяет его.
Я надеюсь, что это будет полезно.
Еще раз спасибо заранее!