You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter WebView遇URL重定向时返回循环的问题解决

问题分析与解决方案

当WebView加载带重定向的URL时,默认会将**重定向源页面(firstpage.html)和目标页面(secondpage.html)**都加入浏览器历史栈。而常规浏览器会自动优化这种场景,只保留最终的目标页面在历史栈中,因此返回时直接退出,不会回到源页面。你的WebView出现循环问题,核心原因就是历史栈中残留了重定向源页面,返回时触发二次重定向。

同时你的代码存在一个明显问题:重复调用setNavigationDelegate,第二次调用会完全覆盖第一次的配置,导致之前的导航规则失效。


修改后的完整代码

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() => runApp(const MaterialApp(home: WebViewExample()));

class WebViewExample extends StatefulWidget {
  const WebViewExample({super.key});

  @override
  State<WebViewExample> createState() => _WebViewExampleState();
}

class _WebViewExampleState extends State<WebViewExample> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.green,
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ElevatedButton(
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => const WebViewExample1()),
              );
            },
            child: const Text("open web view"),
          )
        ],
      ),
    );
  }
}

class WebViewExample1 extends StatefulWidget {
  const WebViewExample1({super.key});

  @override
  State<WebViewExample1> createState() => _WebViewExample1State();
}

class _WebViewExample1State extends State<WebViewExample1> {
  late final WebViewController _controller;
  // 记录初始加载的重定向源页面URL
  final String _initialRedirectUrl = 'http://sample.com/firstpage.html';
  // 标记是否已处理过重定向历史清理
  bool _hasCleanedHistory = false;

  @override
  void initState() {
    super.initState();
    _loadPageFn2();
  }

  void _loadPageFn2() {
    final WebViewController controller = WebViewController();

    controller
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      // 合并NavigationDelegate,避免重复设置导致配置覆盖
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            debugPrint('WebView is loading (progress : $progress%)');
          },
          onPageStarted: (String url) {
            debugPrint('Page started loading: $url');
          },
          onPageFinished: (String url) async {
            debugPrint('Page finished loading: $url');
            // 当页面加载完成,且当前页面是重定向目标,且未清理过历史时执行操作
            if (url != _initialRedirectUrl && !_hasCleanedHistory) {
              // 移除重定向源页面的历史条目
              await controller.removeHistoryEntry();
              setState(() {
                _hasCleanedHistory = true;
              });
            }
          },
          onWebResourceError: (WebResourceError error) {
            debugPrint('''
Page resource error:
  code: ${error.errorCode}
  description: ${error.description}
  errorType: ${error.errorType}
  isForMainFrame: ${error.error.isForMainFrame}
            ''');
          },
          onNavigationRequest: (NavigationRequest request) {
            if (request.url.startsWith('https://www.youtube.com/')) {
              debugPrint('blocking navigation to ${request.url}');
              return NavigationDecision.prevent;
            }
            debugPrint('allowing navigation to ${request.url}');
            return NavigationDecision.navigate;
          },
          onUrlChange: (UrlChange change) {
            debugPrint('url change to ${change.url}');
          },
        ),
      )
      ..loadRequest(Uri.parse(_initialRedirectUrl));

    _controller = controller;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.green,
      body: WillPopScope(
        onWillPop: () async {
          if (await _controller.canGoBack()) {
            _controller.goBack();
            return false;
          } else {
            return true;
          }
        },
        child: WebViewWidget(controller: _controller),
      ),
    );
  }
}

关键修改说明

  1. 合并NavigationDelegate配置:删除重复的setNavigationDelegate调用,确保所有导航规则生效。
  2. 自动清理重定向历史:
    • 用_initialRedirectUrl记录重定向源页面地址
    • 在onPageFinished回调中判断当前页面是否为最终重定向目标,若为是则调用removeHistoryEntry()移除源页面的历史记录
    • 用_hasCleanedHistory标记,避免重复执行清理操作
  3. 保持返回逻辑一致性:清理历史栈后,WebView的历史栈仅保留最终页面,此时点击返回会直接退出WebView,和常规浏览器行为完全一致。

备选方案(兼容旧版本)

如果你的webview_flutter版本不支持removeHistoryEntry,可以修改返回逻辑手动跳过重定向源页面:

onWillPop: () async {
  final List<String> historyUrls = await _controller.getHistoryUrlList();
  if (historyUrls.length > 1) {
    // 检测上一页是否是重定向源页面,若是则直接退出WebView
    if (historyUrls[historyUrls.length - 2] == _initialRedirectUrl) {
      return true;
    } else {
      _controller.goBack();
      return false;
    }
  } else {
    return true;
  }
},

内容的提问来源于stack exchange,提问作者Joe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 01:36:05