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() + ".");
}
_
ほとんどの場合これは問題なく動作します。 数回ただし、例外がスローされます。これは、ディレクトリが存在しないことを意味しますandを作成できませんでした。 100回実行するごとに、95〜96回正常に動作し、4〜5回失敗します。
String myPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getPath();
を使用していますそれで、この問題が発生する理由について何か考えがありますか?
誰かが似たような経験をしたことがありますか?
「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
_であるファイルが存在しないと見なします。これが私のトラブルの原因でしょうか?
編集(2019年8月28日):
今日、私はbountyをこの質問に追加して、さらに注目を集めることができます。質問を注意深く読み、コメントを確認することをお勧めします無視これはRealmからのお客様のサポートに関係していると主張しているコメントです。レルムコードは確かに信頼できない方法を使用しているものですが、-知りたいことが方法が信頼できない理由です。 Realmがこれを回避し、代わりに他のコードを使用できるかどうかは、質問の範囲を超えています。私は単にFile.exists()
を安全に使用できるかどうかを知りたい、そうでない場合はwhy?
改めて、宜しくお願い致します。それが過度に技術的であり、NFSファイルシステム、Java、Android、Linuxなどの深い理解を伴う場合でも、回答を得ることが私にとって本当に重要です。
編集(2019年8月30日):
一部のユーザーはFile.exists()
を他のメソッドに置き換えることを提案しているので、この時点で私が興味を持っていることを述べたいと思います理由を過小評価していますメソッドが失敗しますそしてnot代わりに回避策として使用できるもの。
置き換えたい場合でもFile.exists()
を他の何かで置き換えます私はできませんこのコードは_RealmConfiguration.Java
_にあるため、これを行うことはできませんアプリで使用するレルムライブラリの一部であるファイル(読み取り専用)。
さらにわかりやすくするために、2つのコードを提供します。私の活動で使用するコードと、結果として_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が私のアクティビティに存在し、レルムコードが呼び出されて、突然myFileが存在しなくなります。繰り返しますが、これは一貫性がないことを指摘したいと思います。クラッシュが4〜5%の割合で発生していることに気付いています。これは、ほとんどの場合、myFileがアクティビティ内とレルムコードによるチェックの両方に存在することを意味します。
これがお役に立てば幸いです。
もう一度よろしくお願いします!
まず、Androidを使用している場合、Javaバグデータベースのバグレポートは関係ありません。 Androidは、Sun/Oracleコードベースを使用しません。 Androidは、Javaクラスライブラリのクリーンルームでの再実装として始まりました。
したがって、AndroidのFile.exists()
にバグがある場合、バグはAndroidコードベースにあり、レポートはAndroidにあります課題トラッカー。
しかし、これを言うとき:
これによれば、NFSマウントされたボリュームで操作すると、Java.io.File.existsは最終的にstat(2)を実行します。統計が失敗した場合(いくつかの理由で失敗する場合があります)、_
File.exists
_は(誤って)統計対象のファイルが存在しないと見なします。
File.exists
_cannotはエラーを報告しません。シグネチャは、IOException
をスローすることを許可していません。チェックされていない例外をスローすると、breaking変更になります。それが言うことができるすべてはtrue
またはfalse
です。false
のさまざまな理由を区別したい場合は、代わりに新しいFiles.exists(Path, LinkOptions...)
メソッドを使用する必要があります。これが私のトラブルの原因でしょうか?
はい、できます。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呼び出しがaccess
syscallを実行するネイティブコールを実行しており、syscallが失敗するとErrnoException
をスローすることがわかります。
したがって、今度は、アクセスシスコールの文書化された動作を確認する必要があります。 _man 2 access
_の内容は次のとおりです。
access()は次の場合に失敗します:
EACCES要求されたファイルへのアクセスが拒否されるか、pathnameのパス接頭辞にあるディレクトリの1つに対する検索権限が拒否されます。 (path_resolution(7)も参照してください。)
ELOOPパス名の解決で遭遇したシンボリックリンクが多すぎます。
ENAMETOOLONGパス名が長すぎます
ENOENTパス名のコンポーネントが存在しないか、ぶら下がりシンボリックリンクです
ENOTDIRパス名でディレクトリとして使用されているコンポーネントは、実際にはディレクトリではありません。
EROFS書き込み許可が読み取り専用ファイルシステム上のファイルに要求されました。
access()は次の場合に失敗することがあります:
EFAULTパス名がアクセス可能なアドレス空間の外を指しています
EINVALモードが誤って指定されました。
EIO I/Oエラーが発生しました
ENOMEMカーネルメモリが不足しています。
ETXTBSY実行中の実行可能ファイルへの書き込みアクセスが要求されました。
技術的に不可能または妥当ではないと思われるエラーを取り消しましたが、まだ考慮すべきことがほとんどありません。
別の可能性は、何か(たとえば、アプリケーションの他の部分)がファイルまたは(仮想)シンボリックリンクを削除または名前変更したり、ファイルのアクセス許可を変更したりすることです...
しかし、私はFile.exist()
が壊れているとは思いません1、またはホストOSが壊れている。理論的には可能ですが、理論を裏付けるには明確な証拠が必要です。
1-メソッドの既知の動作と異なる動作をしないという意味では、問題はありません。動作が「正しい」かどうかについて牛が帰ってくるまで議論することができますが、Java 1.0以降はそのようになっており、OpenJDKまたはAndroidでは変更できません。過去20年以上にわたって書かれた何千もの既存のアプリケーションを壊すことなく。それは起こりません。
次はどうする?
まあ、私の推奨は、strace
を使用して、アプリが行っているsyscallを追跡し、access
syscallが予期しない結果を与えている理由についていくつかの手掛かりを得ることができるかどうかを確認することです。例えばパスとは何か、errno
とは何か。 https://source.Android.com/devices/tech/debug/strace を参照してください。
私は同様の問題を抱えていましたが、トラブル率が高く、アンチウイルスがFileSystem
をロックしていたため、リクエストを(ほぼ即座に)失敗していました
回避策は、代わりにJava.nio.Files.exists()
を使用することでした。