Android中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

