安卓WebView加载本地文件触发FileUriExposedException的最优解决方案问询
问题根因分析
首先明确崩溃触发逻辑:你没有主动创建URI是因为WebView默认行为触发了Intent跳转:当你点击HTML中指向图片资源的file://链接时,WebView检测到自身无法直接处理该类资源的预览请求,会自动构造ACTION_VIEW类型的Intent携带file URI调用系统外部应用打开,安卓7.0及以上系统禁止应用对外暴露file URI,因此直接抛出FileUriExposedException崩溃。
现有方案风险评估
你当前通过重写shouldOverrideUrlLoading强制所有链接在WebView内部加载的方案,确实可以阻止WebView向外发送Intent,解决崩溃问题,风险分场景判断:
- 若你的WebView仅加载你自身可控的、存放在指定目录的本地网页,完全不会加载外部不可信网页:该方案没有安全隐患,可正常使用。
- 若你的WebView后续可能加载外部不可信网页:该方案存在两处明显隐患:
- 所有类型的链接都会被强制在WebView加载,包含tel、sms、mailto、APK下载链接等特殊schema,会导致功能异常,甚至被恶意链接诱导执行危险操作
- 你已开启
JavaScriptEnabled,若不可信网页恶意构造跨源读取逻辑,结合你开启的文件访问权限,可能导致本地敏感文件被窃取。
最优解决方案
根据你的业务需求选择对应方案即可:
方案A:不需要外部应用打开资源,所有内容都在WebView展示(最优轻量方案)
在原有拦截逻辑基础上增加规则限制,只允许加载指定目录的本地file协议资源,其他链接按正常逻辑处理,代码如下:
// 先添加安全配置,禁止跨源文件访问,降低风险 mWebView.getSettings().setAllowFileAccessFromFileURLs(false); mWebView.getSettings().setAllowUniversalAccessFromFileURLs(false); // 优化后的WebViewClient逻辑 mWebView.setWebViewClient(new WebViewClient (){ // 安卓7.0及以上调用该方法 @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { Uri uri = request.getUrl(); // 只处理本地file协议链接 if ("file".equals(uri.getScheme())) { String filePath = uri.getPath(); // 校验路径是否属于你允许的目录,防止恶意读取其他文件 if (filePath != null && filePath.startsWith("/storage/emulated/0/fotoshp/")) { view.loadUrl(uri.toString()); return true; } // 不属于允许目录的链接直接拦截,不加载 return true; } // 其他协议走默认逻辑,你也可以根据需求自定义处理 return super.shouldOverrideUrlLoading(view, request); } // 兼容安卓7.0以下版本 @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Uri uri = Uri.parse(url); if ("file".equals(uri.getScheme())) { String filePath = uri.getPath(); if (filePath != null && filePath.startsWith("/storage/emulated/0/fotoshp/")) { view.loadUrl(url); return true; } return true; } return super.shouldOverrideUrlLoading(view, url); } });
方案B:需要允许用户点击图片后用系统相册打开
提前在AndroidManifest中配置好FileProvider,拦截到图片资源后将file URI转换为content URI再发送Intent,代码如下:
mWebView.setWebViewClient(new WebViewClient (){ @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { Uri uri = request.getUrl(); if ("file".equals(uri.getScheme())) { String filePath = uri.getPath(); if (filePath == null || !filePath.startsWith("/storage/emulated/0/fotoshp/")) { return true; } // 判断是否为图片资源 if (filePath.endsWith(".jpg") || filePath.endsWith(".jpeg") || filePath.endsWith(".png") || filePath.endsWith(".webp")) { // 转换为content URI File imgFile = new File(filePath); Uri contentUri = FileProvider.getUriForFile(context, "你的应用包名.fileprovider", imgFile); Intent openIntent = new Intent(Intent.ACTION_VIEW); openIntent.setDataAndType(contentUri, "image/*"); openIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); context.startActivity(openIntent); return true; } // 其他本地资源在WebView加载 view.loadUrl(uri.toString()); return true; } return super.shouldOverrideUrlLoading(view, request); } });
内容的提问来源于stack exchange,提问作者Luis A. Florit
相关产品推荐
相关产品推荐

