You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 18:05:06