问题描述
在 Flutter 开发的网页应用中,当用户刷新浏览器时,虽然页面会显示刷新前的内容(前提是使用了静态路由跳转),但此时调用 Navigator.pop 方法无法回退到上一页。同样,点击浏览器的回退按钮也是无效的,地址栏中的 URL 会变化,但页面内容不会随之改变。
原因分析
当浏览器执行刷新操作时,Flutter 引擎会重新启动并加载当前页面。这意味着刷新后的 Flutter 内存中所有静态变量都被重新初始化,页面栈内之前的页面记录都未保留,只保留了当前的页面状态。这类似于浏览网页时,将其中一页的网址复制出来,在新的标签页再次打开,原有的导航历史并未被继承。
解决方案
(一)设计思路
明确问题根源后,可针对性解决。既然页面栈记录丢失,便需要在代码中自行维护一套备用栈。具体做法是监听页面路由变化,每次进入新页面时记录当前页面的 URL;退出时删除记录的 URL。当浏览器刷新导致栈记录失效时,利用这套备用栈帮助应用回退到上一页。
(二)方案优缺点
优点:
- 可实现回退效果无异常,调用
Navigator.pop方法或点击浏览器回退按钮均支持。
缺点:
Navigator.pushName().then的回调无法生效,因为刷新后生成的是新的上一页实例,并不会自动调用原回调。- 回退后的页面中的临时数据都会消失,例如输入框内的内容、成员变量等。
- 跳转必须使用静态路由的方式,并且传参要用
Uri包裹,不能使用构造函数传参。
实现步骤
1. Web 本地存储工具 — localStorage
localStorage 是 HTML5 提供的存储对象,位于 window 下,以 key-value 形式进行持久化存储。
// 导包
import 'dart:html' as html;
// 使用方式示例
html.window.localStorage['key'] = 'value';
在实际项目中,建议对存储工具进行封装,根据业务需求定义 Key 和 Value 的结构,方便统一调用和管理。
2. 栈记录工具类 RouterHistory
这是一个核心工具类,主要作用是注册事件监听、添加和删除历史记录。
/// DB() 为封装好的本地数据库访问层
class RouterHistory {
/// 监听浏览器刷新前的回调
static Function(html.Event event)? _beforeUnload;
/// 监听浏览器回退时的回调
static Function(html.Event event)? _popState;
/// 目前页面是否被刷新过
static bool isRefresh = false;
/// 初始化与注册监听
static void register() {
// 刷新时回调:标记本地存储为已刷新
_beforeUnload = (event) {
DB(DBKey.isRefresh).value = true;
// 移除刷新前的实例的监听,防止重复触发
html.window.removeEventListener('beforeunload', _beforeUnload);
html.window.removeEventListener('popstate', _popState);
};
// 浏览器回退按钮回调
_popState = (event) {
// 页面被刷新,触发备用回调逻辑
if (isRefresh) {
_back(R.currentContext); // R.currentContext 为当前页面的 Context
}
};
// 添加监听器
html.window.addEventListener('beforeunload', _beforeUnload);
html.window.addEventListener('popstate', _popState);
// 从本地数据库中取出"刷新"标记
isRefresh = DB(DBKey.isRefresh).get(false);
// 如果未被刷新,清除上次备用栈中的历史记录,避免脏数据
if (!isRefresh) {
clean();
}
// 还原本地库中的刷新标记,准备下一次生命周期
DB(DBKey.isRefresh).value = false;
}
// 检查是否能正常返回
static bool checkBack(dynamic currentContext) {
// 如果能正常 pop,则直接返回 true
if (Navigator.canPop(currentContext)) {
return true;
}
// 不能则启用备用栈逻辑
_back(currentContext);
return false;
}
// 执行回退逻辑
static void _back(dynamic currentContext) {
List history = get();
if (history.length > 1) {
history.removeLast();
set(history);
// 跳转至上一页并关闭当前页
Navigator.of(currentContext).popAndPushNamed(history.last);
}
}
// 添加记录
static void add(String? path) {
if (path == null) return;
List history = get();
if (history.contains(path)) return;
history.add(path);
set(history);
}
// 删除记录
static void remove(String? path) {
if (path == null) return;
List history = get();
history.remove(path);
set(history);
}
// 设置备用栈数据
static String set(List<dynamic> history) => DB(DBKey.history).value = json.encode(history);
// 取出备用栈数据
static List<dynamic> get() => json.decode(DB(DBKey.history).get('[]'));
// 清除备用栈
static void clean() => DB(DBKey.history).value = '[]';
}


