Android WebView缓存合规页面邮件显示异常问题求助
首先明确:这不是你的WebView配置问题,是网站使用了Cloudflare的Email Protection功能导致的邮箱混淆。
为什么会出现这个问题?
网站为了防止爬虫抓取邮箱地址,用Cloudflare的反爬机制把真实邮箱加密了。正常在浏览器访问时,Cloudflare会自动加载一段解密JS(就是你看到的/cdn-cgi/l/email-protection路径的脚本),把混淆的邮箱还原成正常的mailto:链接。但你下载缓存的是原始网页源码,里面的邮箱是加密后的状态,而且当你在WebView里加载本地缓存页时,大概率无法加载Cloudflare的外部解密脚本,所以邮箱显示异常且无法点击。
解决方案
这里提供两种可行的解决思路:
方案1:缓存执行完JS后的最终DOM内容
不要直接下载网页的原始源码,而是等网页在WebView(或者Headless Chrome)里完全加载、JS执行完毕后,获取渲染后的最终HTML内容作为缓存。这样邮箱已经被解密成正常的mailto:格式了。
在Android里,你可以在onPageFinished回调中获取最终DOM:
webView.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); // 获取渲染后的完整HTML if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { view.evaluateJavascript("document.documentElement.outerHTML;", new ValueCallback<String>() { @Override public void onReceiveValue(String html) { // 把html内容保存为缓存文件 } }); } else { // 低版本可以用loadUrl的方式获取 view.loadUrl("javascript:window.HTMLOUT.processHTML(document.documentElement.outerHTML);"); } } });
(低版本需要定义一个JS接口来接收HTML内容,核心是拿到渲染后的DOM即可)
方案2:手动注入解密JS修复缓存页
如果你只能使用现有的缓存源码(原始加密后的HTML),可以在WebView加载缓存页后,手动注入一段解密JS,还原邮箱地址和链接。
Cloudflare的邮箱解密逻辑是公开的:data-cfemail属性里的十六进制字符串,第一个字节是密钥,后面的每个字节和密钥异或就能得到真实邮箱的字符。
你可以在onPageFinished回调中注入这段解密脚本:
webView.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); // 注入解密邮箱的JS代码 String decodeScript = "function decodeCFEmail() {" + " var cfElements = document.querySelectorAll('.__cf_email__');" + " cfElements.forEach(function(el) {" + " var encoded = el.getAttribute('data-cfemail');" + " if (!encoded) return;" + " var key = parseInt(encoded.substr(0, 2), 16);" + " var decoded = '';" + " for (var i = 2; i < encoded.length; i += 2) {" + " decoded += String.fromCharCode(parseInt(encoded.substr(i, 2), 16) ^ key);" + " }" + " // 还原邮箱文本" + " el.textContent = decoded;" + " // 修复父级a标签的mailto链接" + " var parentLink = el.parentNode;" + " if (parentLink && parentLink.tagName === 'A') {" + " parentLink.href = 'mailto:' + decoded;" + " }" + " });" + "}" + "decodeCFEmail();"; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { view.evaluateJavascript(decodeScript, null); } else { view.loadUrl("javascript:" + decodeScript); } } });
这段脚本会遍历页面中所有带有__cf_email__类的元素,解密出真实邮箱,同时修复对应的a标签的href属性,让邮箱可以正常点击唤起邮件客户端。
总结
两种方案各有优劣:方案1更彻底,缓存的内容和正常网页完全一致,但需要修改缓存逻辑;方案2不需要修改缓存,只需要在加载时注入JS,适合快速修复现有问题。
内容的提问来源于stack exchange,提问作者eManna

