FileUriExposedExceptionの修正:Android 7.0以降でfile:///によるクラッシュを止める方法

intermediate📱 Android2026-04-24| Android 7.0 (APIレベル 24) 以上

Error Message

android.os.FileUriExposedException: file:///... exposed beyond app through Intent.getData()
#android#fileprovider#intent#uri#android-nougat

なぜアプリがクラッシュするのかアプリのtargetSdkVersionを24以上にアップグレードした際、アプリがクラッシュすることはありませんか?それはおそらくFileUriExposedExceptionが発生しているためです。これは、PDFを開いたり、JPEGを共有したり、カメラを起動したりする際によく起こります。Android 7.0 (Nougat) ではセキュリティが強化され、アプリの境界を越えてfile:// URIを渡すことが禁止されました。

スタックトレースは通常、以下のようになります:

android.os.FileUriExposedException: file:///storage/emulated/0/Download/invoice.pdf exposed beyond app through Intent.getData()

セキュリティの変更点Androidの初期のころは、ファイルを共有することは家の鍵を直接手渡すようなものでした。ディスク上の絶対パスを渡すだけでよかったのです。しかし、受け取り側のアプリがその特定のフォルダへの読み取り権限を持っていない場合、結局失敗してしまいます。API 24以降、AndroidはStrictModeポリシーを適用し、この動作を禁止しました。現在は、content:// URIを使用する必要があります。これは一時的な「ゲストパス」のように機能し、他のアプリに対してその1つのファイルへの特定かつ限定的なアクセスを許可します。

正しい修正方法:FileProviderの実装AndroidXライブラリのFileProviderクラスは、この問題を解決するための標準的な方法です。これは、ファイルのための安全で仮想的なブリッジを作成します。

1. マニフェストにProviderを登録するまず、Androidシステムに対してアプリがファイルプロバイダーであることを伝えます。AndroidManifest.xml<application>タグ内に、以下の<provider>ブロックを追加します。

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

${applicationId}.fileproviderを使用することで、オーソリティ(authority)文字列のユニークさが保証されます。これにより、ユーザーのデバイス上で他のアプリと衝突することを防ぎます。

2. 共有フォルダのマッピング次に、res/xml/file_paths.xmlに新しいファイルを作成します。共有しても安全なフォルダを明示的にリストアップする必要があります。FileProviderは、ここで定義されていないものには一切アクセスしません。

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <!-- context.getFilesDir() にマップされます -->
    <files-path name="internal_files" path="." />

    <!-- context.getCacheDir() にマップされます -->
    <cache-path name="internal_cache" path="." />

    <!-- Environment.getExternalStorageDirectory() にマップされます -->
    <external-path name="sd_card" path="." />
</paths>

3. File URIをContent URIに変更する最後に、JavaまたはKotlinのコードを更新します。Uri.fromFile(file)FileProvider版に置き換えます。PDFを安全に共有する方法は以下の通りです:

// クラッシュする古い方法:
// Uri uri = Uri.fromFile(invoiceFile);

// 安全な方法:
Uri contentUri = FileProvider.getUriForFile(
    context, 
    context.getPackageName() + ".fileprovider", 
    invoiceFile
);

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(contentUri, "application/pdf");

// このフラグを追加しないと、受け取り側のアプリはブロックされたままになります
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

context.startActivity(intent);

「救命ボート」的な回避策(本番用ではありません)レガシーなコードベースをメンテナンスしていて、リファクタリングの合間に5分で修正が必要な場合は、システムにセキュリティチェックを無視させるように強制できます。以下のコードをActivity.onCreate()メソッドに記述します:

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());

この方法は控えめに使用してください。クラッシュは止まりますが、根本的な権限の問題は解決されません。受け取り側のアプリがストレージ権限を持っていないため、依然としてファイルを開けない可能性があります。

修正の確認方法公開する前に、以下の3つのポイントを再確認してください:

  • Logcatを確認する: URIをログ出力してください。content://com.example.app.fileprovider/internal_files/invoice.pdfのような形式になっているはずです。- API 24以上を対象にする: 常にAndroid 7.0以降を実行しているエミュレータでテストしてください。古いデバイス(API 23以下)はfile:// URIを気にしなかったため、バグが顕在化しません。- アプリ間の受け渡しをテストする: Google DriveやVLCなどのサードパーティ製アプリでファイルを開いてみてください。「ファイルが見つかりません」というエラーが表示される場合は、FLAG_GRANT_READ_URI_PERMISSIONフラグを忘れている可能性があります。

Related Error Notes