Android WebView加载RecyclerView内PDF链接偶现内容不显示
问题根因与修复方案
核心Bug定位
- 固定1秒延迟逻辑完全不可靠:Dialog View树完成挂载、WebView完成初始化与布局测量的耗时没有固定值,1秒的经验延迟无法适配不同性能的设备。当WebView未拿到真实宽高时加载页面,Google Docs预览页的JS会计算得到0尺寸的容器,直接渲染空白;当RecyclerView快速切换条目时,延迟任务还未执行,WebView已被回收,也会出现加载无响应。
- PDF源URL未做编码:直接将原PDF地址拼接到Google Docs预览接口后,一旦原URL包含查询参数、特殊字符、中文,会直接截断预览接口的参数,导致实际拿到的PDF地址不完整,这是你遇到“部分场景正常、部分场景不显示”的核心诱因。
- WebView基础配置缺失:当前仅开启JS与缩放权限,未开启DOM本地存储、缓存支持,而Google Docs预览页强依赖localStorage存储渲染状态,配置缺失会导致JS执行中断,出现白屏。
- WebViewClient行为不一致:重载了两个版本的
shouldOverrideUrlLoading方法,高版本方法直接调用super,其默认实现会拦截页面内部的跳转请求,导致Google Docs的加载流程中断,在不同系统版本上表现差异极大。 - 加载时序不匹配:
postDelayed的加载任务没有和WebView的窗口挂载状态绑定,当WebView还未显示到窗口时就触发加载,Google Docs的JS会检测到页面不可见,主动暂停渲染流程。
逐点修复实现
1. 替换固定延迟逻辑,绑定布局完成回调
删掉原有postDelayed(1000)逻辑,改用WebView布局回调判断容器真实尺寸,确保加载时机正确:
webView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { // 仅当WebView拿到有效宽高时触发加载,仅执行一次 if (right > 0 && bottom > 0) { webView.removeOnLayoutChangeListener(this); String pdfUrl = list.get(position).getURl(); // 对原PDF URL做编码,避免特殊字符截断预览接口参数 String encodedPdfUrl = Uri.encode(pdfUrl); webView.loadUrl("http://docs.google.com/gview?embedded=true&url=" + encodedPdfUrl); } } });
2. 补全WebView必要配置
在原有设置基础上新增缺失项,解决JS执行中断问题:
WebSettings settings = webView.getSettings(); settings.setJavaScriptEnabled(true); settings.setBuiltInZoomControls(true); settings.setSupportZoom(true); settings.setLoadWithOverviewMode(true); settings.setUseWideViewPort(true); // 新增必要配置 settings.setDomStorageEnabled(true); settings.setCacheMode(WebSettings.LOAD_DEFAULT); settings.setAllowContentAccess(true); settings.setAllowFileAccess(true);
3. 统一WebViewClient行为,新增失败兜底
统一两个版本的URL拦截逻辑,避免内部请求被拦截,同时增加空页面校验与重试机制:
private class Callback extends WebViewClient { // 兼容低版本系统,不拦截任何跳转 @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { return false; } // 兼容高版本系统,同样不拦截内部跳转,不要调用super @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { return false; } @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { handler.proceed(); } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); // 校验页面是否真实渲染完成,Google Docs加载完成后会生成canvas渲染层 view.evaluateJavascript( "(function(){ return document.getElementsByTagName('canvas').length > 0 || document.body.innerText.trim().length > 0; })();", value -> { if (!Boolean.parseBoolean(value)) { // 检测到空页面,触发重试 view.reload(); } } ); } @Override public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { super.onReceivedError(view, request, error); // 主框架加载失败时自动重试 if (request.isForMainFrame()) { view.reload(); } } }
4. 清理快速切换时的干扰任务
在Dialog关闭回调中主动释放WebView资源,避免旧的加载任务干扰新页面:
g.setOnDismissListener(dialog -> { webView.removeAllViews(); webView.destroy(); });
注:不要再通过
postDelayed加固定延迟掩盖时序问题,这类方式会因为设备性能差异、用户操作速度变化随时复现白屏,绑定真实的布局状态、渲染结果回调才是稳定实现的核心。
内容的提问来源于stack exchange,提问作者Ahmed Elsayed
相关产品推荐
相关产品推荐

