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

Android中Advanced WebView无法识别下载请求的问题咨询

解决Advanced WebView无法识别前端生成的下载链接问题

我之前也碰到过完全一样的情况,Advanced WebView自带的onDownloadRequested方法只能捕获服务器端主动发起的下载请求(比如返回Content-Disposition: attachment响应头的链接),但像Editpad这类网站的下载是通过前端JavaScript生成Blob/Data URI实现的——这种客户端侧的下载行为不会触发原生的下载回调,这就是为什么你的代码在Unsplash(直接指向文件的链接)能正常工作,但在Editpad失效的核心原因。

下面是具体的解决方案,分步骤实现:

1. 配置WebView的基础支持

首先确保WebView开启了JavaScript执行权限和必要的文件访问权限,这是捕获前端下载事件的前提:

AdvancedWebView webView = findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setAllowFileAccess(true);
// Android 11+ 若不需要跨域文件访问,可省略后两项配置
webView.getSettings().setAllowFileAccessFromFileURLs(true);
webView.getSettings().setAllowUniversalAccessFromFileURLs(true);

2. 注入JavaScript捕获前端下载行为

我们需要给加载的网页注入一段JS脚本,监听所有带download属性的<a>标签点击,同时处理动态生成的Blob链接,把下载信息传递给Android原生代码:

webView.setWebViewClient(new AdvancedWebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        // 注入JS脚本
        String captureDownloadJs = "(function() {" +
                "// 监听所有带download属性的a标签点击" +
                "document.addEventListener('click', function(e) {" +
                "var target = e.target;" +
                "if(target.tagName === 'A' && target.hasAttribute('download')) {" +
                "e.preventDefault(); // 阻止默认无反应的行为" +
                "var downloadUrl = target.href;" +
                "var filename = target.getAttribute('download');" +
                "// 调用Android原生方法" +
                "window.android.onDownloadTriggered(downloadUrl, filename);" +
                "}" +
                "});" +
                "// 拦截Blob URL的创建,记录动态生成的下载链接" +
                "var originalCreateObjectURL = window.URL.createObjectURL;" +
                "window.URL.createObjectURL = function(blob) {" +
                "var url = originalCreateObjectURL(blob);" +
                "window.android.onBlobUrlCreated(url);" +
                "return url;" +
                "};" +
                "})()";
        webView.loadUrl("javascript:" + captureDownloadJs);
    }
});

3. 创建JS与原生的交互桥梁

定义一个Java类作为中间层,接收前端传递的下载信息,并处理不同类型的下载链接:

public class DownloadBridge {
    private Context mContext;
    private AdvancedWebView mWebView;

    public DownloadBridge(Context context, AdvancedWebView webView) {
        mContext = context;
        mWebView = webView;
    }

    @JavascriptInterface
    public void onDownloadTriggered(String url, String filename) {
        if (url.startsWith("blob:")) {
            // Blob类型链接需要转成base64才能保存到本地
            String convertBlobJs = "(function() {" +
                    "fetch('" + url + "')" +
                    ".then(res => res.blob())" +
                    ".then(blob => {" +
                    "var reader = new FileReader();" +
                    "reader.onload = function() {" +
                    "window.android.onBlobConverted(reader.result, '" + filename + "');" +
                    "};" +
                    "reader.readAsDataURL(blob);" +
                    "})" +
                    "})()";
            mWebView.loadUrl("javascript:" + convertBlobJs);
        } else {
            // 普通链接直接调用系统浏览器打开下载
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(Uri.parse(url));
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            mContext.startActivity(intent);
        }
    }

    @JavascriptInterface
    public void onBlobConverted(String base64Data, String filename) {
        // 解析base64数据并保存到本地
        try {
            String[] dataParts = base64Data.split(",");
            String mimeType = dataParts[0].split(":")[1].split(";")[0];
            byte[] fileBytes = Base64.decode(dataParts[1], Base64.DEFAULT);

            // 保存到应用私有目录(无需动态权限)
            File downloadDir = mContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
            if (!downloadDir.exists()) downloadDir.mkdirs();
            File targetFile = new File(downloadDir, filename);

            FileOutputStream fos = new FileOutputStream(targetFile);
            fos.write(fileBytes);
            fos.close();

            // 通知系统更新媒体库,让用户能在文件管理器找到文件
            MediaScannerConnection.scanFile(mContext,
                    new String[]{targetFile.getAbsolutePath()},
                    new String[]{mimeType},
                    null);

            Toast.makeText(mContext, "文件已保存:" + filename, Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(mContext, "下载失败:" + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

    @JavascriptInterface
    public void onBlobUrlCreated(String url) {
        // 可选:记录Blob URL用于调试
        Log.d("DownloadBridge", "生成Blob链接:" + url);
    }
}

4. 绑定交互桥梁到WebView

最后把DownloadBridge绑定到WebView上,让前端JS能调用原生方法:

// "android"是前端JS中调用的对象名,要和注入脚本里的名字一致
webView.addJavascriptInterface(new DownloadBridge(this, webView), "android");

5. 权限适配(关键)

如果你的App需要保存文件到公共下载目录,记得在AndroidManifest.xml中添加权限:

<!-- Android 9及以下需要 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Android 10+ 若使用应用私有目录则无需上述权限,如需访问公共目录需额外配置 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

优先推荐使用应用私有目录(代码中的getExternalFilesDir),不需要动态申请权限,更安全合规。

这样处理后,无论是服务器端发起的下载(依然会触发onDownloadRequested),还是前端JS生成的Blob/Data URI下载,都能被正确捕获并处理了。

内容的提问来源于stack exchange,提问作者ProfessorAF

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:29:06