Sửa lỗi FileUriExposedException: Cách khắc phục lỗi crash file:/// trên Android 7.0+

intermediate📱 Android2026-04-24| Android 7.0 (API Level 24) trở lên

Error Message

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

Tại sao ứng dụng của bạn bị crashBạn vừa nâng cấp targetSdkVersion của ứng dụng lên 24 hoặc cao hơn và gặp lỗi crash? Khả năng cao là bạn đang gặp lỗi FileUriExposedException. Lỗi này thường xảy ra nhất khi bạn cố gắng mở file PDF, chia sẻ ảnh JPEG hoặc kích hoạt camera. Android 7.0 (Nougat) đã thắt chặt bảo mật bằng cách chặn các URI file:// vượt qua ranh giới ứng dụng.

Stack trace của bạn thường sẽ trông như thế này:

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

Sự thay đổi về bảo mậtTrong những phiên bản Android đầu tiên, việc chia sẻ một file giống như việc đưa cho ai đó chìa khóa vật lý của một căn phòng trong nhà bạn. Bạn chỉ cần gửi một đường dẫn trực tiếp trên ổ đĩa. Tuy nhiên, nếu ứng dụng nhận không có quyền đọc thư mục cụ thể đó, nó vẫn sẽ thất bại. Bắt đầu từ API 24, Android thực thi chính sách StrictMode cấm hành vi này. Giờ đây, bạn phải sử dụng URI content://. Nó hoạt động như một thẻ khách tạm thời, cấp cho ứng dụng khác quyền truy cập cụ thể và giới hạn vào đúng một file đó.

Giải pháp đúng đắn: Triển khai FileProviderClass FileProvider trong thư viện AndroidX là giải pháp tiêu chuẩn để khắc phục vấn đề này. Nó tạo ra một cầu nối ảo, bảo mật cho các tệp tin của bạn.

1. Đăng ký Provider trong ManifestĐầu tiên, hãy thông báo cho hệ thống Android rằng ứng dụng của bạn hiện là một file provider. Thêm khối <provider> này vào bên trong thẻ <application> của tệp AndroidManifest.xml.

<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>

Việc sử dụng ${applicationId}.fileprovider đảm bảo chuỗi authority là duy nhất. Điều này giúp ứng dụng của bạn không bị xung đột với các ứng dụng khác trên thiết bị của người dùng.

2. Ánh xạ các thư mục chia sẻTiếp theo, tạo một tệp mới tại res/xml/file_paths.xml. Bạn phải liệt kê rõ ràng những thư mục nào là an toàn để chia sẻ. FileProvider sẽ không chạm vào bất cứ thứ gì bạn không định nghĩa ở đây.

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <!-- Ánh xạ tới context.getFilesDir() -->
    <files-path name="internal_files" path="." />

    <!-- Ánh xạ tới context.getCacheDir() -->
    <cache-path name="internal_cache" path="." />

    <!-- Ánh xạ tới Environment.getExternalStorageDirectory() -->
    <external-path name="sd_card" path="." />
</paths>

3. Chuyển đổi từ URI File sang ContentCuối cùng, cập nhật mã Java hoặc Kotlin của bạn. Thay thế Uri.fromFile(file) bằng phiên bản FileProvider. Dưới đây là cách chia sẻ tệp PDF một cách an toàn:

// Cách cũ, gây lỗi crash:
// Uri uri = Uri.fromFile(invoiceFile);

// Cách an toàn:
Uri contentUri = FileProvider.getUriForFile(
    context, 
    context.getPackageName() + ".fileprovider", 
    invoiceFile
);

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

// Thêm flag này nếu không ứng dụng nhận vẫn sẽ bị chặn
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

context.startActivity(intent);

Phương pháp "Cứu sinh" (Không dùng cho môi trường Production)Nếu bạn đang bảo trì một codebase cũ và cần một bản sửa lỗi nhanh trong 5 phút trong khi chờ refactor, bạn có thể buộc hệ thống bỏ qua kiểm tra bảo mật. Hãy đặt mã này vào phương thức Activity.onCreate() của bạn:

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

Hãy sử dụng cách này một cách hạn chế. Mặc dù nó ngăn chặn lỗi crash, nhưng nó không giải quyết vấn đề phân quyền cốt lõi. Ứng dụng nhận vẫn có thể thất bại khi mở tệp vì thiếu quyền truy cập bộ nhớ.

Cách xác minh bản sửa lỗiTrước khi phát hành, hãy kiểm tra kỹ ba điểm sau:

  • Kiểm tra Logcat: Ghi log URI của bạn. Nó sẽ trông như thế này: content://com.example.app.fileprovider/internal_files/invoice.pdf.- Target API 24+: Luôn kiểm tra trên trình giả lập chạy Android 7.0 hoặc mới hơn. Các thiết bị cũ hơn (API 23 trở xuống) không quan tâm đến URI file://, vì vậy chúng sẽ không làm lộ lỗi này.- Kiểm tra việc bàn giao (handover): Mở file trong một ứng dụng bên thứ ba như Google Drive hoặc VLC. Nếu chúng hiển thị lỗi "File Not Found", có khả năng bạn đã bỏ lỡ flag FLAG_GRANT_READ_URI_PERMISSION.

Related Error Notes