1. WebView 基础加载与生命周期绑定
任务拆解:Activity 生命周期与 WebView 状态绑定
本案例的核心不是“把网页显示出来”,而是让 WebView 的生命周期与 Activity 的生命周期严格对齐。WebView 内部持有独立的渲染进程、JavaScript 引擎和页面状态,如果 Activity 销毁时 WebView 没有同步释放,就会造成内存泄漏;如果 Activity 暂停时 WebView 仍在执行 JS 定时器或媒体播放,就会造成后台耗电。因此,我们需要在 onCreate 中创建并加载页面,在 onResume / onPause 中同步恢复与暂停,在 onDestroy 中销毁 WebView 并解除引用。
关键实现:创建、加载与生命周期回调
下面给出一个最小可运行的 Activity 实现。它加载 assets/demo.html,并在四个生命周期节点打印 WebView 的关键状态,便于用 Logcat 观察变化。
public class MainActivity extends AppCompatActivity {
private WebView webView;
private static final String TAG = "WebViewLifecycle";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = findViewById(R.id.web_view);
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true); // 允许 JS,便于观察定时器行为
settings.setDomStorageEnabled(true); // 启用 localStorage
webView.setWebViewClient(new WebViewClient()); // 保持在应用内加载
webView.loadUrl("file:///android_asset/demo.html");
Log.d(TAG, "onCreate: webView=" + webView + ", url=" + webView.getUrl());
}
@Override
protected void onResume() {
super.onResume();
webView.onResume(); // 恢复 JS 定时器、媒体等
webView.resumeTimers();
Log.d(TAG, "onResume: progress=" + webView.getProgress()
+ ", title=" + webView.getTitle());
}
@Override
protected void onPause() {
webView.onPause(); // 暂停 JS 定时器、媒体等
webView.pauseTimers();
Log.d(TAG, "onPause: progress=" + webView.getProgress()
+ ", url=" + webView.getUrl());
super.onPause();
}
@Override
protected void onDestroy() {
if (webView != null) {
webView.stopLoading();
webView.loadUrl("about:blank"); // 清空页面,释放渲染资源
webView.clearHistory();
webView.removeAllViews();
webView.destroy(); // 必须调用,否则泄漏
webView = null;
}
Log.d(TAG, "onDestroy: webView released");
super.onDestroy();
}
}
注意:
webView.onPause() 与 webView.pauseTimers() 作用不同。前者暂停当前页面的渲染与 JS 执行,后者暂停所有 WebView 实例的定时器。本案例中两者配合使用,可以更彻底地停止后台活动。assets/demo.html 示例页面
为了观察生命周期对 JS 的影响,页面中放置一个每秒递增的计数器,并在页面可见性变化时输出日志。
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Lifecycle Demo</title></head>
<body>
<h3>WebView 生命周期演示</h3>
<p>计数器:<span id="count">0</span></p>
<script>
let n = 0;
setInterval(() => {
n++;
document.getElementById('count').textContent = n;
console.log('tick ' + n);
}, 1000);
document.addEventListener('visibilitychange', () => {
console.log('visibility=' + document.visibilityState);
});
</script>
</body>
</html>
生命周期状态变化图解
下图展示了 Activity 四个阶段与 WebView 内部状态、JS 定时器、页面渲染之间的对应关系。箭头方向表示状态迁移,右侧标注了每个阶段应执行的关键操作与观察点。
验证与观察方法
- 运行应用,观察 Logcat 中
onCreate与onResume的输出,确认 URL 已加载且进度为 100。 - 按 Home 键使 Activity 进入后台,观察
onPause日志,同时页面中的计数器应停止递增。 - 重新回到应用,观察
onResume日志,计数器应从暂停时的数值继续递增,而不是重置。 - 按返回键退出 Activity,观察
onDestroy日志,确认 WebView 已销毁且无内存泄漏警告。
如果
onDestroy 中遗漏 webView.destroy(),WebView 会持有 Activity 的 Context 引用,导致 Activity 无法被 GC 回收。在 Android Studio 的 Memory Profiler 中反复进出页面,可以观察到 Activity 实例数量持续上升。常见问题与排查
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 页面白屏 | assets 路径拼写错误或文件未放入 assets 目录 | 确认文件位于 src/main/assets/demo.html,URL 使用 file:///android_asset/ 前缀 |
| JS 不执行 | 未开启 JavaScript | 调用 settings.setJavaScriptEnabled(true) |
| 返回后台后计数器仍在跑 | 只调用了 onPause() 未调用 pauseTimers() | 两者配合使用 |
| 退出后内存不释放 | 未调用 destroy() 或未将引用置 null | 在 onDestroy 中完整释放 |