Android WebView加载固定耗时30秒问题排查求助
问题背景
我们的Android应用采用原生登录页,登录后进入带WebView的菜单Activity,每个菜单对应独立Activity且都有专属WebView,所有相关Activity均继承自SPBaseWebActivity基类。
此前运行正常,但一年前开始出现WebView加载固定耗时30秒的问题,通过WebViewClient统计指标确认耗时始终为30秒。
关键现象
- HTML无远程依赖(所有资源打包在APK内),无AJAX请求
- 仅保留"Hello world"的极简HTML可瞬间加载
- 只要包含基础标签外的内容(如CSS链接),加载就卡30秒
- 30秒内UI和logcat完全阻塞,无网络请求(Network Inspector确认)
已尝试无效操作
- 开启/关闭硬件加速
- 清空HTML内容
- 禁用WebView调试模式
- 移除WebViewClient
- 加载google.com替代本地文件
- 精简HTML内容
- 卸载/更新Android System WebView
- 开启/关闭Cookie管理
相关代码
OptionActivity.java
public class OptionActivity extends SPBaseWebActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_web_calendar); this.webview = (WebView) findViewById(R.id.webview); DebugManager.log("Entered calendar activity"); DebugManager.clearPerformanceCheckpoints("calendar_view"); DebugManager.addPerformanceCheckpoint("calendar_view", "onCreate finished"); } @Override protected void onResume() { super.onResume(); DebugManager.addPerformanceCheckpoint("calendar_view", "onResume start"); String url = "calendar.html"; setupWebview(url); reloadOnResume = false; DebugManager.addPerformanceCheckpoint("calendar_view", "onResume finished - " + (System.currentTimeMillis() / 1000)); } @Override protected void onPageLoaded(WebView view, String url) { super.onPageLoaded(view, url); DebugManager.addPerformanceCheckpoint("calendar_view", "onPageLoaded finished - " + (System.currentTimeMillis() / 1000)); } }
SPBaseWebActivity.java
public class SPBaseWebActivity implements WebAppInterfaceDelegate { protected WebView webview = null; protected WebAppInterface jsInterface = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //Remove title bar this.requestWindowFeature(Window.FEATURE_NO_TITLE); if (this.webview == null) this.webview = (WebView)findViewById(R.id.webview); setupWebview(null); } protected void setupWebview(String url) { if (this.webview != null) { if (url == null || url.length() == 0) url = "base/empty.html"; else if (!url.contains("http")) url = "app/" + url; MVWebChromeClient mvwcc = new MVWebChromeClient(); mvwcc.parentActivity = this; this.webview.setWebChromeClient(mvwcc); DebugManager.addPerformanceCheckpoint("webview_loading", "Webview client init - " + (System.currentTimeMillis() / 1000)); this.webview.setWebViewClient(new WebViewClient() { @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); DebugManager.addPerformanceCheckpoint("webview_loading", "onPageStarted - " + (System.currentTimeMillis() / 1000)); } @Override public void onPageFinished(WebView view, String url) { if (android.os.Build.VERSION.SDK_INT >= 21) { CookieManager.getInstance().setAcceptThirdPartyCookies(view, true); CookieManager.getInstance().flush(); } else CookieManager.getInstance().setAcceptCookie(true); DebugManager.addPerformanceCheckpoint("webview_loading", "onPageLoaded - " + (System.currentTimeMillis() / 1000)); onPageLoaded(view, url); } }); DebugManager.addPerformanceCheckpoint("webview_loading", "Webview client created - " + (System.currentTimeMillis() / 1000)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { WebView.setWebContentsDebuggingEnabled(Globals.DEBUG); } if (!url.contains("http")) this.webview.loadUrl("file:///android_asset/pages/" + url); else this.webview.loadUrl(url); this.webview.getSettings().setJavaScriptEnabled(true); this.webview.getSettings().setAllowFileAccessFromFileURLs(true); this.webview.getSettings().setAllowUniversalAccessFromFileURLs(true); this.webview.getSettings().setDomStorageEnabled(true); this.jsInterface = new WebAppInterface(this); this.jsInterface.delegate = this; this.jsInterface.parentActivity = this; if (android.os.Build.VERSION.SDK_INT >= 21) { CookieManager.getInstance().setAcceptThirdPartyCookies(this.webview, true); CookieManager.getInstance().flush(); } else CookieManager.getInstance().setAcceptCookie(true); this.webview.addJavascriptInterface(this.jsInterface, "Android"); this.webview.requestFocus(); } } }
calendar.html
<!DOCTYPE html> <html> <head> <meta charset="utf8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>MV Reports - Agendamento</title> <link rel="stylesheet" href="../../mv/css/bootstrap.min.css" /> <link rel="stylesheet" href="../../mv/addons/font-awesome/css/font-awesome.min.css" /> <link rel="stylesheet" href="../../mv/addons/material2/css/materialdesignicons.min.css" /> </head> <body> Takes 30 to load </body> </html>
排查与解决建议
验证本地资源路径有效性
HTML中CSS使用相对路径../../mv/css/...,WebView加载本地asset文件时,相对路径以当前HTML所在位置为基准。确认file:///android_asset/pages/app/calendar.html对应的上级路径是否存在目标资源,若路径错误,WebView内部可能因尝试加载不存在的资源触发隐性超时,导致主线程阻塞。调整WebView初始化顺序
当前代码先调用loadUrl再配置WebSettings和JS接口,可能导致WebView未完成配置就开始加载,引发内部阻塞。建议修改setupWebview方法顺序:先配置WebSettings、设置客户端、添加JS接口,最后调用loadUrl。抓取主线程调用栈定位阻塞点
30秒固定超时+UI阻塞,大概率是主线程被WebView同步操作卡住。用Android Studio Profiler抓取阻塞期间的主线程调用栈,查看具体执行逻辑,定位是资源加载、JS引擎初始化还是其他同步操作导致的停滞。禁用WebView自动资源加载
部分WebView版本会自动预加载关联资源,若资源路径异常可能触发超时。可先禁用自动加载图片测试:webview.getSettings().setLoadsImagesAutomatically(false); webview.getSettings().setBlockNetworkImage(true);若加载恢复正常,再逐步排查具体是哪个资源导致的问题。
清理WebView缓存与数据
本地资源加载异常可能与WebView缓存损坏有关,测试阶段可调用以下代码清理:WebStorage.getInstance().deleteAllData(); webview.clearCache(true); CookieManager.getInstance().removeAllCookies(null);
内容的提问来源于stack exchange,提问作者Sagito

