Android WebView下载XML文件崩溃:无Activity处理Intent求助
WebView下载XML文件崩溃问题修复
错误原因
从崩溃日志可以明确看到,问题出在你构造的Uri格式完全错误://download:blob:http:mydownloadurl。系统找不到能识别这个非法Uri的Activity,因此抛出ActivityNotFoundException。错误根源是代码中手动添加的//download:前缀,完全破坏了原始下载链接的格式。
修复方案
1. 修正DownloadListener逻辑
替换原有的下载监听代码,同时兼容普通HTTP/HTTPS链接和WebView特有的blob:协议链接:
mWebView.setDownloadListener(new DownloadListener() { public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { // 处理WebView生成的blob临时链接 if (url.startsWith("blob:")) { // 通过JS获取blob内容并调用原生方法保存 String jsFetchCode = "fetch('" + url + "').then(res => res.blob()).then(blob => {" + " const reader = new FileReader();" + " reader.onloadend = () => Android.saveFile(reader.result, '" + contentDisposition + "', '" + mimetype + "');" + " reader.readAsDataURL(blob);" + "});"; mWebView.evaluateJavascript(jsFetchCode, null); } else { // 处理普通HTTP/HTTPS下载链接 Uri downloadUri = Uri.parse(url); Intent viewIntent = new Intent(Intent.ACTION_VIEW, downloadUri); viewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 先检查是否有应用能处理该Intent,避免再次崩溃 if (viewIntent.resolveActivity(getPackageManager()) != null) { startActivity(viewIntent); } else { Toast.makeText(MainActivity.this, "未找到可处理下载的应用", Toast.LENGTH_SHORT).show(); } } } });
2. 添加JS交互接口处理blob保存
上面的代码调用了Android.saveFile方法,需要给WebView注册对应的JS交互类,用于接收blob内容并保存为文件:
// 定义JS交互类 public class JsFileSaver { private Context mContext; public JsFileSaver(Context context) { mContext = context; } @JavascriptInterface public void saveFile(String dataUrl, String contentDisposition, String mimetype) { // 解析base64格式的文件内容 String[] dataParts = dataUrl.split(","); byte[] fileBytes = Base64.decode(dataParts[1], Base64.DEFAULT); // 从contentDisposition中提取文件名,默认用download.xml String fileName = "download.xml"; if (contentDisposition != null) { Pattern fileNamePattern = Pattern.compile("filename=\"?([^\"]+)\"?"); Matcher matcher = fileNamePattern.matcher(contentDisposition); if (matcher.find()) { fileName = matcher.group(1); } } // 适配Android版本保存文件 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android 10+ 使用MediaStore保存 ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName); values.put(MediaStore.MediaColumns.MIME_TYPE, mimetype); values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS); Uri saveUri = mContext.getContentResolver().insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values); if (saveUri != null) { try (OutputStream outputStream = mContext.getContentResolver().openOutputStream(saveUri)) { outputStream.write(fileBytes); Toast.makeText(mContext, "文件已保存至下载目录", Toast.LENGTH_SHORT).show(); } catch (IOException e) { e.printStackTrace(); Toast.makeText(mContext, "文件保存失败", Toast.LENGTH_SHORT).show(); } } } else { // Android 9及以下 使用外部存储保存(需WRITE权限) File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); File targetFile = new File(downloadDir, fileName); try (FileOutputStream outputStream = new FileOutputStream(targetFile)) { outputStream.write(fileBytes); Toast.makeText(mContext, "文件已保存至:" + targetFile.getAbsolutePath(), Toast.LENGTH_SHORT).show(); } catch (IOException e) { e.printStackTrace(); Toast.makeText(mContext, "文件保存失败", Toast.LENGTH_SHORT).show(); } } } }
然后在onCreate方法中注册这个JS接口:
// 注册JS交互接口(确保已开启JavaScriptEnabled) mWebView.addJavascriptInterface(new JsFileSaver(this), "Android");
3. 添加必要权限
在AndroidManifest.xml中添加权限:
<!-- Android 9及以下需要的存储权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <!-- 网络权限(访问网站必需) --> <uses-permission android:name="android.permission.INTERNET" />
4. 优化WebViewClient的链接处理
原有的shouldOverrideUrlLoading会强制所有链接在WebView中打开,可能导致无法处理外部链接的情况,建议修改为只拦截自己网站的链接:
@Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // 只在WebView中打开自己网站的链接 if (url.startsWith("http://yourwebsite.com") || url.startsWith("https://yourwebsite.com")) { view.loadUrl(url); return true; } else { // 外部链接交给系统浏览器处理 Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); startActivity(intent); return true; } }
注意事项
- 原代码中
onReceivedSslError直接调用handler.proceed()会忽略SSL证书错误,这在生产环境中存在安全风险,建议仅在测试环境使用,正式环境需提示用户或终止加载。
内容的提问来源于stack exchange,提问作者Habie Putra
相关产品推荐
相关产品推荐

