WebView加载本地HTML后点击链接崩溃,报FileUriExposedException错误
Got it, let's resolve this crash you're facing. The root cause here is Android's security restriction introduced in API level 24 (Android 7.0): apps are blocked from exposing file:// URIs to other components (even within your own app) via Intents. When your WebView tries to navigate to a local asset link like file:///android_asset/page2.html, it triggers this FileUriExposedException and crashes the app.
Here are two reliable solutions, with the first being the official recommended approach:
1. Use WebViewAssetLoader (AndroidX, Recommended)
Google provides the WebViewAssetLoader as part of the AndroidX WebKit library to safely load local assets/files in WebView without exposing risky file:// URIs. It uses a secure fake HTTPS domain to serve your assets.
Step 1: Add the dependency
Add this to your module-level build.gradle file (use the latest available version):
dependencies { implementation 'androidx.webkit:webkit:1.5.0' }
Step 2: Configure WebView with the asset loader
In your Activity/Fragment where you initialize the WebView:
WebView webView = findViewById(R.id.your_webview_id); WebSettings webSettings = webView.getSettings(); webSettings.setJavaScriptEnabled(true); // Enable if your HTML uses JS // Initialize the asset loader to serve files from the assets folder final WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder() .addPathHandler("/assets/", new WebViewAssetLoader.AssetsPathHandler(this)) .build(); // Set a custom WebViewClient to intercept requests and use the asset loader webView.setWebViewClient(new WebViewClient() { @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { // Let the asset loader handle requests to local assets return assetLoader.shouldInterceptRequest(request.getUrl()); } // Optional: Ensure links open within the WebView instead of triggering an Intent @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { view.loadUrl(request.getUrl().toString()); return true; } }); // Load your initial HTML using the secure asset loader URL webView.loadUrl("https://appassets.androidplatform.net/assets/index.html");
How it works
All links in your HTML (like <a href="page2.html">) will automatically resolve to the secure HTTPS-like URI, avoiding any file:// exposure. This is the cleanest, most future-proof solution.
2. Custom ContentProvider (For non-AndroidX projects)
If you can't use AndroidX, create a custom ContentProvider to serve your assets via safe content:// URIs.
Step 1: Create the ContentProvider class
public class AssetContentProvider extends ContentProvider { private static final String AUTHORITY = "com.your.package.name.assetprovider"; // Replace with your app's package private static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY); // Helper method to convert asset paths to content URIs public static Uri getAssetUri(String assetPath) { return BASE_URI.buildUpon().appendEncodedPath(assetPath).build(); } @Override public boolean onCreate() { return true; } @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { String assetPath = uri.getEncodedPath().substring(1); // Remove leading '/' return getContext().getAssets().openFd(assetPath).getParcelFileDescriptor(); } @Override public String getType(Uri uri) { String assetPath = uri.getEncodedPath().substring(1); return MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(assetPath)); } }
Step 2: Register the provider in AndroidManifest.xml
Add this inside the <application> tag:
<provider android:name=".AssetContentProvider" android:authorities="com.your.package.name.assetprovider" // Match the AUTHORITY above android:exported="false" />
Step 3: Update WebView setup
WebView webView = findViewById(R.id.your_webview_id); webView.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // Convert any file:///android_asset/ links to content URIs if (url.startsWith("file:///android_asset/")) { String assetPath = url.substring("file:///android_asset/".length()); Uri contentUri = AssetContentProvider.getAssetUri(assetPath); view.loadUrl(contentUri.toString()); return true; } return super.shouldOverrideUrlLoading(view, url); } }); // Load initial HTML using the content URI webView.loadUrl(AssetContentProvider.getAssetUri("index.html").toString());
This approach wraps your assets in content:// URIs, which are allowed to be used within your app without triggering the exception.
Whichever method you choose, make sure to test on devices running Android 7.0+ to confirm the crash is resolved.
内容的提问来源于stack exchange,提问作者Ashonko

