《Flutter全栈开发实战指南:从零到高级》- 23 -混合开发与WebView

混合开发为何如此重要?

在实际项目中,我们常面临这样的困境:业务需要快速迭代,但原生发版周期长;H5页面体验不佳,但开发速度快。混合开发正是解决这一矛盾的最佳平衡点

graph TD A[业务需求] --> B{开发方案选择} B --> C[原生开发] B --> D[Web开发] B --> E[混合开发] C --> F[优势: 性能最佳] C --> G[劣势: 迭代慢, 双端开发] D --> H[优势: 跨平台, 热更新] D --> I[劣势: 体验差, 能力受限] E --> J[融合两者优势] E --> K[平衡性能与效率] J --> L[原生体验 + Web灵活性] K --> M[快速迭代 + 一致体验]

一:WebView核心原理

1.1 WebView的本质是什么?

很多人以为WebView只是一个内置浏览器,其实远不止如此。WebView实际上是一个微型浏览器,它包含了HTML解析器、CSS渲染器、JavaScript引擎等完整组件。

graph TB subgraph "WebView内部架构" A[WebView容器] --> B[渲染引擎] A --> C[JavaScript引擎] A --> D[网络模块] B --> E[HTML解析器] B --> F[CSS渲染器] B --> G[布局引擎] C --> H[V8/JSCore引擎] D --> I[网络请求处理] end subgraph "Flutter侧" J[Dart VM] --> K[Flutter Engine] K --> L[Skia渲染] end A -.-> K C -.-> J

WebView和Flutter运行在不同的隔离环境中:

  • Flutter:运行在Dart VM,使用Skia渲染
  • WebView:运行在浏览器引擎中,有自己的渲染管线

1.2 Flutter中的WebView实现原理

Flutter的WebView并不是自己实现的浏览器引擎,而是对原生WebView的桥接封装

Platform Channels工作原理

dart 复制代码
// Flutter调用原生方法的流程
1. Dart代码调用WebViewController的方法
2. 通过MethodChannel将二进制消息发送到原生端
3. 原生端调用对应的WebView API
4. 结果通过MethodChannel返回Dart

二:封装WebView

2.1 基础封装

先看一个在实际项目中使用的WebView封装,这个版本已经处理了大部分常见问题:

dart 复制代码
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class TestWebView extends StatefulWidget {
  final String url;
  final Map<String, String>? headers;
  
  const TestWebView({
    Key? key,
    required this.url,
    this.headers,
  }) : super(key: key);
  
  @override
  _TestWebViewState createState() => _TestWebViewState();
}

class _TestWebViewState extends State<TestWebView> {
  // 控制器
  late WebViewController _controller;
  
  // 状态管理
  double _progress = 0.0;
  bool _isLoading = true;
  bool _hasError = false;
  String? _pageTitle;
  
  @override
  void initState() {
    super.initState();
    _initWebView();
  }
  
  void _initWebView() {
    _controller = WebViewController()
      // 1. 基础配置
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(Colors.transparent)
      
      // 2. 注册JavaScript通信通道
      ..addJavaScriptChannel(
        'FlutterBridge',
        onMessageReceived: (message) {
          _handleJavaScriptMessage(message.message);
        },
      )
      
      // 3. 导航
      ..setNavigationDelegate(
        NavigationDelegate(
          onPageStarted: (url) {
            setState(() {
              _progress = 0.0;
              _isLoading = true;
              _hasError = false;
            });
          },
          
          onProgress: (progress) {
            setState(() => _progress = progress / 100.0);
          },
          
          onPageFinished: (url) async {
            // 获取页面标题
            final title = await _controller.getTitle();
            setState(() {
              _pageTitle = title;
              _isLoading = false;
            });
            
            // 注入自定义脚本
            await _injectCustomScripts();
          },
          
          // URL拦截
          onNavigationRequest: (request) {
            return _handleNavigation(request);
          },
        ),
      )
      
      // 4. 加载页面
      ..loadRequest(
        Uri.parse(widget.url),
        headers: widget.headers ?? {},
      );
  }
  
  // 处理JS消息
  void _handleJavaScriptMessage(String message) {
    try {
      final data = jsonDecode(message);
      final type = data['type'];
      final payload = data['data'];
      
      switch (type) {
        case 'userAction':
          _handleUserAction(payload);
          break;
        case 'getUserInfo':
          _sendUserInfoToWeb();
          break;
      }
    } catch (e) {
      print('JS消息解析失败: $e');
    }
  }
  
  // URL导航处理逻辑
  NavigationDecision _handleNavigation(NavigationRequest request) {
    final url = request.url;
    
    // 白名单
    if (!_isUrlInWhitelist(url)) {
      return NavigationDecision.prevent;
    }
    
    // 链接处理
    if (url.startsWith('myapp://')) {
      _handleDeepLink(url);
      return NavigationDecision.prevent;
    }
    
    return NavigationDecision.navigate;
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _buildAppBar(),
      body: Stack(
        children: [
          // WebView主体
          WebViewWidget(controller: _controller),
          
          // 进度条
          if (_isLoading && _progress < 1.0)
            LinearProgressIndicator(
              value: _progress,
              backgroundColor: Colors.grey[200],
            ),
          
          // 错误状态
          if (_hasError)
            _buildErrorWidget(),
        ],
      ),
      // 底部导航栏
      bottomNavigationBar: _buildBottomBar(),
    );
  }
}

2.2 核心功能点

2.2.1 JavaScript通信原理

JavaScript与Flutter的通信是通过桥接实现的:

sequenceDiagram participant W as WebView(JS环境) participant B as JavaScriptChannel participant F as Flutter(Dart环境) participant H as 消息处理器 W->>B: window.FlutterBridge.postMessage(JSON) B->>F: 通过Platform Channel传递消息 F->>H: 解析并处理消息 H->>F: 返回处理结果 F->>W: _controller.runJavaScript()

核心技术点

  • 消息序列化:所有数据必须转为JSON串
  • 异步处理:异步通信并处理回调
  • 错误处理:JS或Flutter都可能出错,需要有错误处理

2.2.2 性能优化

dart 复制代码
class WebViewOptimizer {
  // 1. 缓存
  static void setupCache(WebViewController controller) async {
    await controller.runJavaScript('''
      // 启用Service Worker缓存
      if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('/sw.js');
      }
      
      // 本地存储
      if (window.localStorage) {
        localStorage.setItem('lastVisit', new Date().toISOString());
      }
    ''');
  }
  
  // 2. 内存管理
  static void manageMemory(WebViewController controller) {
    // 清理缓存
    Timer.periodic(Duration(minutes: 5), (_) {
      controller.clearCache();
    });
  }
  
  // 3. 预加载
  static Future<void> preloadWebView({
    required String url,
    required BuildContext context,
  }) async {
    // 提前初始化WebView但不显示
    final controller = WebViewController();
    await controller.loadRequest(Uri.parse(url));
    
    // 保存到全局缓存
    WebViewCache.instance.cache(url, controller);
  }
}

三:混合应用设计

3.1 分层架构

一个良好的混合架构应该分为四个层次:

graph TB subgraph "1.表现层 Presentation Layer" A1[Flutter原生页面] A2[WebView容器] A3[混合页面] end subgraph "2.桥接层 Bridge Layer" B1[JavaScript Bridge] B2[消息路由器] B3[协议编解码器] end subgraph "3.业务层 Business Layer" C1[用户服务] C2[支付服务] C3[数据服务] end subgraph "4.基础设施层 Infrastructure" D1[WebView池] D2[缓存管理器] D3[网络层] D4[安全模块] end A1 --> B1 A2 --> B1 A3 --> B1 B1 --> B2 B2 --> C1 B2 --> C2 B2 --> C3 C1 --> D4 C2 --> D3 C3 --> D2 A2 --> D1

3.2 路由管理

混合应用最复杂的是路由管理。我们需要决定什么时候用原生页面,什么时候用WebView。

dart 复制代码
class HybridRouter {
  static final Map<String, RouteConfig> _routeTable = {
    '/home': RouteConfig(
      type: RouteType.native,
      path: '/home',
      webUrl: null,
    ),
    '/product/:id': RouteConfig(
      type: RouteType.hybrid,
      path: '/product/:id',
      webUrl: 'https://api.xxxx.com/product/{id}',
      nativeFallback: '/productDetail',
    ),
    '/promotion/:code': RouteConfig(
      type: RouteType.web,
      path: '/promotion/:code',
      webUrl: 'https://promo.xxxx.com/{code}',
    ),
  };
  
  // 路由
  static Future<void> navigateTo({
    required BuildContext context,
    required String path,
    Map<String, dynamic>? params,
  }) async {
    final config = _findRouteConfig(path);
    
    if (config == null) {
      // WebView
      await _openWebView(context, path, params);
      return;
    }
    
    final useWeb = await _shouldUseWebVersion(config);
    
    if (useWeb) {
      await _openWebView(context, config.webUrl!, params);
    } else {
      await _openNativePage(context, config.nativeFallback!, params);
    }
  }
  
  // 条件
  static Future<bool> _shouldUseWebVersion(RouteConfig config) async {
    // 1. 检查网络状况
    final connectivity = await Connectivity().checkConnectivity();
    if (connectivity == ConnectivityResult.none) {
      return false; // 离线时用原生
    }
    
    // 2. 检查用户偏好
    final prefs = await SharedPreferences.getInstance();
    final preferNative = prefs.getBool('prefer_native') ?? false;
    
    // 3. 检查页面类型
    switch (config.type) {
      case RouteType.native:
        return false;
      case RouteType.web:
        return true;
      case RouteType.hybrid:
        return await _businessDecision(config);
    }
  }
}

3.3 状态管理

混合应用的状态管理比纯原生应用更复杂,因为状态可能在三个地方:

markdown 复制代码
状态存储位置:
1. Flutter/Dart状态
2. WebView/JavaScript状态  
3. 原生平台状态(iOS/Android)

实现方案:

dart 复制代码
class HybridStateManager {
  // 状态存储
  final Map<String, dynamic> _globalState = {};
  
  // 状态同步方法
  Future<void> syncStateToWeb(WebViewController controller) async {
    final stateJson = jsonEncode(_globalState);
    await controller.runJavaScript('''
      // 更新Web端状态
      window.appState = $stateJson;
      
      // 触发状态更新事件
      window.dispatchEvent(new CustomEvent('appStateChanged', {
        detail: $stateJson
      }));
    ''');
  }
  
  // 从Web接收状态更新
  void handleStateFromWeb(Map<String, dynamic> newState) {
    _globalState.addAll(newState);
    
    // 通知Flutter组件
    _stateNotifier.value = {..._globalState};
    
    // 持久化
    _persistState();
  }
}

四:通信协议

4.1 消息协议

良好的通信从定义协议开始,实际项目中使用的协议规范,如下:

dart 复制代码
// 定义消息协议
class BridgeMessage {
  final String id;           // 消息ID
  final String type;         // 消息类型
  final String method;       // 方法名
  final dynamic data;        // 消息数据
  final int timestamp;       // 时间戳
  final String? callbackId;  // 回调ID
  
  // 消息类型
  static const String TYPE_REQUEST = 'request';
  static const String TYPE_RESPONSE = 'response';
  static const String TYPE_EVENT = 'event';
  
  // 常用方法
  static const String METHOD_GET_USER_INFO = 'getUserInfo';
  static const String METHOD_PAYMENT = 'startPayment';
  static const String METHOD_SHARE = 'shareContent';
  
  // 序列化
  String toJson() {
    return jsonEncode({
      'id': id,
      'type': type,
      'method': method,
      'data': data,
      'timestamp': timestamp,
      'callbackId': callbackId,
    });
  }
  
  // 反序列化
  static BridgeMessage fromJson(String jsonStr) {
    final map = jsonDecode(jsonStr);
    return BridgeMessage(
      id: map['id'],
      type: map['type'],
      method: map['method'],
      data: map['data'],
      timestamp: map['timestamp'],
      callbackId: map['callbackId'],
    );
  }
}

4.2 通信流程

sequenceDiagram participant H as H5页面(JS) participant B as JavaScript Bridge participant D as Dart消息分发器 participant S as 业务服务 participant N as 原生功能 H->>B: 发送请求
BridgeMessage Note over H,B: 1. 用户点击购买按钮 B->>D: 通过Channel传递 Note over B,D: 2. 平台通道传输 D->>D: 解析验证消息 Note over D: 3. 安全检查与验证 alt 需要原生功能 D->>N: 调用原生模块 N->>D: 返回结果 else 需要业务服务 D->>S: 调用业务服务 S->>D: 返回业务数据 end D->>B: 构造响应消息 B->>H: 返回结果 Note over B,H: 6. 更新H5页面状态

4.3 错误处理

dart 复制代码
class BridgeErrorHandler {
  // 定义错误码
  static const Map<int, String> errorCodes = {
    1001: '网络连接失败',
    1002: '用户未登录',
    1003: '参数验证失败',
    1004: '权限不足',
    1005: '服务端错误',
  };
  
  // 统一错误处理
  static BridgeMessage handleError(
    dynamic error, 
    String messageId,
    String method,
  ) {
    int code = 1005; // 默认错误码
    String message = '未知错误';
    
    if (error is PlatformException) {
      code = int.parse(error.code);
      message = error.message ?? '平台异常';
    } else if (error is HttpException) {
      code = 1001;
      message = '网络请求失败';
    }
    
    return BridgeMessage(
      id: messageId,
      type: BridgeMessage.TYPE_RESPONSE,
      method: method,
      data: {
        'success': false,
        'error': {
          'code': code,
          'message': errorCodes[code] ?? message,
          'detail': error.toString(),
        },
      },
      timestamp: DateTime.now().millisecondsSinceEpoch,
    );
  }
}

五:性能优化

5.1 WebView启动优化

WebView首次启动慢是常见问题。我们可以通过预加载和复用来优化:

dart 复制代码
// 实现WebView池
class WebViewPool {
  static final Map<String, WebViewController> _pool = {};
  static final Map<String, DateTime> _lastUsed = {};
  
  // 获取WebView
  static Future<WebViewController> getWebView({
    required String key,
    required Future<WebViewController> Function() builder,
  }) async {
    // 1. 检查池中是否有可复用的
    if (_pool.containsKey(key)) {
      _lastUsed[key] = DateTime.now();
      return _pool[key]!;
    }
    
    // 2. 创建新的WebView
    final controller = await builder();
    _pool[key] = controller;
    _lastUsed[key] = DateTime.now();
    
    // 3. 清理过期缓存
    _cleanup();
    
    return controller;
  }
  
  // 预加载
  static Future<void> preload(List<String> urls) async {
    for (final url in urls) {
      final controller = WebViewController();
      await controller.loadRequest(Uri.parse(url));
      _pool[url] = controller;
    }
  }
}

5.2 内存管理

WebView是内存消耗大户,需要精细管理:

dart 复制代码
class WebViewMemoryManager {
  // 处理内存压力
  static void setupMemoryPressureHandler() {
    SystemChannels.lifecycle.setMessageHandler((msg) async {
      if (msg == AppLifecycleState.paused.toString()) {
        // App进入后台,释放WebView内存
        await _releaseWebViewMemory();
      } else if (msg == AppLifecycleState.resumed.toString()) {
        // App回到前台,恢复必要状态
        await _restoreWebViewState();
      }
      return null;
    });
  }
  
  static Future<void> _releaseWebViewMemory() async {
    // 1. 清除缓存
    for (final controller in WebViewPool._pool.values) {
      await controller.clearCache();
    }
    
    // 2. 卸载不活动的WebView
    final now = DateTime.now();
    WebViewPool._pool.entries
      .where((entry) {
        final lastUsed = WebViewPool._lastUsed[entry.key];
        return lastUsed != null && 
               now.difference(lastUsed) > Duration(minutes: 10);
      })
      .forEach((entry) {
        WebViewPool._pool.remove(entry.key);
        WebViewPool._lastUsed.remove(entry.key);
      });
  }
}

5.3 渲染性能优化

dart 复制代码
class WebViewPerformance {
  // 启用硬件加速
  static void enableHardwareAcceleration(WebViewController controller) {
    controller.runJavaScript('''
      // 启用CSS硬件加速
      const style = document.createElement('style');
      style.textContent = \`
        .animate-element {
          transform: translateZ(0);
          will-change: transform;
        }
        .fixed-element {
          position: fixed;
          backface-visibility: hidden;
        }
      \`;
      document.head.appendChild(style);
    ''');
  }
  
  // 监控性能指标
  static void setupPerformanceMonitor(WebViewController controller) {
    controller.runJavaScript('''
      // 使用Performance API监控
      const observer = new PerformanceObserver((list) => {
        const entries = list.getEntries();
        entries.forEach(entry => {
          if (entry.duration > 100) { 
            console.warn('长任务:', entry.name, entry.duration);
            
            // 发送到Flutter监控
            if (window.FlutterBridge) {
              window.FlutterBridge.postMessage(JSON.stringify({
                type: 'performance',
                data: {
                  metric: 'long_task',
                  name: entry.name,
                  duration: entry.duration,
                  timestamp: Date.now()
                }
              }));
            }
          }
        });
      });
      
      observer.observe({entryTypes: ['longtask']});
    ''');
  }
}

六:安全防护

6.1 多层安全防护

graph TD subgraph "安全防护体系" A[输入层防护] --> B[通信层防护] B --> C[执行层防护] C --> D[数据层防护] end subgraph "具体措施" A1[URL白名单验证] A2[输入参数过滤] B1[HTTPS强制] B2[消息签名] B3[防重放攻击] C1[JS沙盒隔离] C2[权限最小化] D1[数据加密] D2[本地存储安全] end A1 --> A A2 --> A B1 --> B B2 --> B B3 --> B C1 --> C C2 --> C D1 --> D D2 --> D

6.2 具体实现

dart 复制代码
class WebViewSecurity {
  // 验证URL白名单
  static final List<RegExp> _urlWhitelist = [
    RegExp(r'^https://api\.xxxx\.com/'),
    RegExp(r'^https://cdn\.xxxx\.com/'),
    RegExp(r'^https://sso\.xxxx\.com/'),
  ];
  
  static bool isUrlAllowed(String url) {
    return _urlWhitelist.any((pattern) => pattern.hasMatch(url));
  }
  
  // 验证消息签名
  static bool verifyMessageSignature(
    Map<String, dynamic> message,
    String signature,
  ) {
    // 1. 检查时间戳
    final timestamp = message['timestamp'];
    final now = DateTime.now().millisecondsSinceEpoch;
    if ((now - timestamp).abs() > 300000) { // 5分钟有效期
      return false;
    }
    
    // 2. 验证签名
    final secretKey = 'your_secret_key_here';
    final dataToSign = '${message['id']}:${timestamp}:$secretKey';
    final expectedSig = sha256.convert(utf8.encode(dataToSign)).toString();
    
    return expectedSig == signature;
  }
  
  // 防XSS注入
  static String sanitizeInput(String input) {
    // 移除危险标签和属性
    return input
        .replaceAll(RegExp(r'<script[^>]*>.*?</script>', caseSensitive: false), '')
        .replaceAll(RegExp(r'on\w+="[^"]*"', caseSensitive: false), '')
        .replaceAll(RegExp(r'javascript:', caseSensitive: false), '')
        .replaceAll(RegExp(r'data:', caseSensitive: false), '');
  }
}

6.3 Content Security Policy

dart 复制代码
Future<void> setupContentSecurityPolicy(WebViewController controller) async {
  await controller.runJavaScript('''
    // 添加CSP Meta标签
    const cspMeta = document.createElement('meta');
    cspMeta.httpEquiv = 'Content-Security-Policy';
    cspMeta.content = \`
      default-src 'self' https://api.xxxx.com;
      script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.xxxx.com;
      style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
      img-src 'self' data: https:;
      font-src 'self' https://fonts.gstatic.com;
      connect-src 'self' https://api.xxxx.com wss://ws.xxxx.com;
      frame-ancestors 'self';
      form-action 'self' https://sso.xxxx.com;
    \`;
    
    document.head.appendChild(cspMeta);
    
    // 禁用危险API
    Object.defineProperty(window, 'eval', {
      value: function() {
        console.warn('eval() is disabled for security reasons');
        return null;
      }
    });
    
    // 监控可疑行为
    const originalPostMessage = window.postMessage;
    window.postMessage = function(message, targetOrigin) {
      if (!targetOrigin || targetOrigin === '*') {
        console.warn('postMessage without targetOrigin is restricted');
        return;
      }
      return originalPostMessage.call(this, message, targetOrigin);
    };
  ''');
}

七:调试

7.1 集成调试工具

dart 复制代码
class WebViewDebugger {
  // 启用远程调试
  static void enableRemoteDebugging(WebViewController controller) {
    // Android: Chrome DevTools
    // iOS: Safari Web Inspector
    
    controller.runJavaScript('''
      console.log = function(...args) {
        // 重定向console到Flutter
        const message = args.map(arg => 
          typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
        ).join(' ');
        
        if (window.FlutterBridge) {
          window.FlutterBridge.postMessage(JSON.stringify({
            type: 'console',
            level: 'log',
            message: message,
            timestamp: Date.now()
          }));
        }
        
        // 保留原始console功能
        originalConsoleLog.apply(console, args);
      };
      
      const originalConsoleLog = console.log;
    ''');
  }
}

7.2 监控错误与上报

dart 复制代码
class WebViewErrorMonitor {
  static final List<WebViewError> _errors = [];
  
  static void setupErrorMonitoring(WebViewController controller) {
    // 监控JS错误
    controller.runJavaScript('''
      window.addEventListener('error', function(event) {
        const errorData = {
          message: event.message,
          filename: event.filename,
          lineno: event.lineno,
          colno: event.colno,
          error: event.error?.toString(),
          stack: event.error?.stack,
          timestamp: Date.now(),
          url: window.location.href
        };
        
        if (window.FlutterBridge) {
          window.FlutterBridge.postMessage(JSON.stringify({
            type: 'error',
            data: errorData
          }));
        }
      }, true);
      
      // 监控未处理的Promise拒绝
      window.addEventListener('unhandledrejection', function(event) {
        const errorData = {
          type: 'promise_rejection',
          reason: event.reason?.toString(),
          timestamp: Date.now()
        };
        
        if (window.FlutterBridge) {
          window.FlutterBridge.postMessage(JSON.stringify({
            type: 'error',
            data: errorData
          }));
        }
      });
    ''');
  }
  
  // 上报error到服务端
  static Future<void> reportErrors() async {
    if (_errors.isEmpty) return;
    
    try {
      await http.post(
        Uri.parse('https://api.xxxx.com/error-report'),
        body: jsonEncode({
          'appVersion': '1.0.0',
          'platform': Platform.operatingSystem,
          'errors': _errors,
        }),
        headers: {'Content-Type': 'application/json'},
      );
      
      _errors.clear();
    } catch (e) {
      print('错误上报失败: $e');
    }
  }
}

八:以电商混合应用为例

8.1 项目架构

下面我们通过一个电商App的案例,把前面所有知识点串联起来:

bash 复制代码
lib/
├── main.dart
├── core/
│   ├── hybrid/           # 混合开发
│   │   ├── manager.dart     # 混合管理器
│   │   ├── bridge.dart      # 桥接文件
│   │   ├── router.dart      # 混合路由
│   │   └── security.dart    # 安全模块
│   └── di/               # 依赖注入
├── modules/
│   ├── product/          # 商品模块
│   │   ├── list_page.dart   # 原生列表
│   │   └── detail_page.dart # WebView详情
│   ├── cart/             # 购物车模块
│   └── order/            # 订单模块
└── shared/
    ├── widgets/          # 共享组件
    ├── utils/            # 工具类
    └── constants/        # 常量定义

8.2 实现商品详情页

dart 复制代码
class ProductDetailPage extends StatefulWidget {
  final String productId;
  
  const ProductDetailPage({Key? key, required this.productId}) 
      : super(key: key);
  
  @override
  _ProductDetailPageState createState() => _ProductDetailPageState();
}

class _ProductDetailPageState extends State<ProductDetailPage> {
  late WebViewController _controller;
  final ProductService _productService = ProductService();
  
  @override
  void initState() {
    super.initState();
    _initWebView();
    _prefetchProductData();
  }
  
  void _initWebView() {
    // 从WebView池获取或创建
    _controller = WebViewPool.getWebView(
      key: 'product_${widget.productId}',
      builder: () => _createWebViewController(),
    );
  }
  
  Future<WebViewController> _createWebViewController() async {
    final controller = WebViewController();
    
    // 获取用户信息和商品数据
    final userInfo = await UserService().getCurrentUser();
    final productData = await _productService.getProduct(widget.productId);
    
    // 含参URL
    final url = _buildProductUrl(productData, userInfo);
    
    await controller.loadRequest(Uri.parse(url));
    
    return controller;
  }
  
  String _buildProductUrl(Product product, User? user) {
    final params = {
      'product_id': product.id,
      'product_name': Uri.encodeComponent(product.name),
      'price': product.price.toString(),
      'user_id': user?.id ?? '',
      'user_token': user?.token ?? '',
      'platform': Platform.operatingSystem,
      'app_version': '1.0.0',
      'timestamp': DateTime.now().millisecondsSinceEpoch.toString(),
    };
    
    // 添加签名
    final signature = _generateSignature(params);
    params['sign'] = signature;
    
    final uri = Uri.parse('https://m.xxxx.com/product/detail')
        .replace(queryParameters: params);
    
    return uri.toString();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('商品详情'),
        actions: _buildAppBarActions(),
      ),
      body: Column(
        children: [
          // 顶部:商品简介
          _buildProductSummary(),
          
          // WebView详情部分
          Expanded(
            child: WebViewWidget(controller: _controller),
          ),
          
          // 底部:原生操作栏
          _buildBottomActionBar(),
        ],
      ),
    );
  }
  
  Widget _buildProductSummary() {
    return Container(
      padding: EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        border: Border(bottom: BorderSide(color: Colors.grey[200]!)),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            '商品名称',
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
          SizedBox(height: 8),
          Row(
            children: [
              Text(
                '¥ 299.00',
                style: TextStyle(
                  fontSize: 24,
                  color: Colors.red,
                  fontWeight: FontWeight.bold,
                ),
              ),
              SizedBox(width: 8),
              Text(
                '¥ 399.00',
                style: TextStyle(
                  fontSize: 16,
                  color: Colors.grey,
                  decoration: TextDecoration.lineThrough,
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
  
  Widget _buildBottomActionBar() {
    return Container(
      height: 60,
      decoration: BoxDecoration(
        color: Colors.white,
        border: Border(top: BorderSide(color: Colors.grey[200]!)),
      ),
      child: Row(
        children: [
          // 客服
          Expanded(
            child: TextButton.icon(
              onPressed: _contactCustomerService,
              icon: Icon(Icons.chat),
              label: Text('客服'),
            ),
          ),
          
          // 加入购物车
          Expanded(
            child: ElevatedButton.icon(
              onPressed: _addToCart,
              icon: Icon(Icons.shopping_cart),
              label: Text('加入购物车'),
              style: ElevatedButton.styleFrom(
                primary: Colors.orange,
              ),
            ),
          ),
          
          // 立即购买
          Expanded(
            child: ElevatedButton.icon(
              onPressed: _buyNow,
              icon: Icon(Icons.shopping_bag),
              label: Text('立即购买'),
              style: ElevatedButton.styleFrom(
                primary: Colors.red,
              ),
            ),
          ),
        ],
      ),
    );
  }
  
  Future<void> _addToCart() async {
    // 通过桥接通知H5页面
    await _controller.runJavaScript('''
      if (window.addToCart) {
        window.addToCart();
      } else {
        // 调用Flutter原生方法
        window.FlutterBridge.postMessage(JSON.stringify({
          type: 'action',
          method: 'addToCart',
          data: {productId: '${widget.productId}'}
        }));
      }
    ''');
  }
}

8.3 适配H5页面

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>商品详情</title>
    <script>
        // Flutter桥接适配
        class FlutterAdapter {
            constructor() {
                this.callbacks = new Map();
                this.messageId = 0;
                this.setupBridge();
            }
            
            setupBridge() {
                // 注册Flutter调用方法
                window.addToCart = () => this.addToCart();
                window.buyNow = () => this.buyNow();
                window.getUserInfo = () => this.getUserInfo();
                
                // 初始化消息监听
                if (window.FlutterBridge) {
                    console.log('Flutter桥接已OK');
                }
            }
            
            // 添加购物车
            async addToCart() {
                const productId = this.getQueryParam('product_id');
                
                try {
                    // 通过桥接调用Flutter
                    const result = await this.callFlutter('addToCart', {
                        productId: productId,
                        quantity: 1
                    });
                    
                    if (result.success) {
                        this.showToast('添加成功');
                    } else {
                        this.showToast('添加失败: ' + result.message);
                    }
                } catch (error) {
                    console.error('添加购物车失败:', error);
                    this.showToast('网络异常,请重试');
                }
            }
            
            // 调用Flutter方法
            callFlutter(method, data) {
                return new Promise((resolve, reject) => {
                    const messageId = ++this.messageId;
                    
                    this.callbacks.set(messageId, { resolve, reject });
                    
                    // 设置超时
                    setTimeout(() => {
                        if (this.callbacks.has(messageId)) {
                            this.callbacks.delete(messageId);
                            reject(new Error('请求超时'));
                        }
                    }, 10000);
                    
                    // 发送消息
                    window.FlutterBridge.postMessage(JSON.stringify({
                        id: messageId.toString(),
                        type: 'request',
                        method: method,
                        data: data,
                        timestamp: Date.now()
                    }));
                });
            }
            
            // 接收Flutter消息
            onFlutterMessage(message) {
                try {
                    const data = JSON.parse(message);
                    
                    if (data.type === 'response' && data.id) {
                        const callback = this.callbacks.get(parseInt(data.id));
                        if (callback) {
                            this.callbacks.delete(parseInt(data.id));
                            
                            if (data.data.success) {
                                callback.resolve(data.data);
                            } else {
                                callback.reject(new Error(data.data.message));
                            }
                        }
                    } else if (data.type === 'event') {
                        // 处理Flutter发来的事件
                        this.handleEvent(data);
                    }
                } catch (error) {
                    console.error('处理Flutter消息失败:', error);
                }
            }
            
            getQueryParam(name) {
                const urlParams = new URLSearchParams(window.location.search);
                return urlParams.get(name);
            }
            
            showToast(message) {
                // 显示提示
                const toast = document.createElement('div');
                toast.textContent = message;
                toast.style.cssText = `
                    position: fixed;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                    background: rgba(0,0,0,0.8);
                    color: white;
                    padding: 12px 24px;
                    border-radius: 8px;
                    z-index: 1000;
                `;
                document.body.appendChild(toast);
                
                setTimeout(() => {
                    document.body.removeChild(toast);
                }, 2000);
            }
        }
        
        // 页面初始化
        document.addEventListener('DOMContentLoaded', function() {
            const adapter = new FlutterAdapter();
            
            // 检测运行环境
            const isInApp = navigator.userAgent.includes('FlutterWebView');
            
            if (isInApp) {
                // App内特有逻辑
                document.body.classList.add('in-app');
                
                // 适配安全区域
                document.documentElement.style.setProperty(
                    '--safe-area-top', 
                    'env(safe-area-inset-top, 0px)'
                );
                document.documentElement.style.setProperty(
                    '--safe-area-bottom', 
                    'env(safe-area-inset-bottom, 0px)'
                );
                
                // 隐藏H5导航
                const h5Nav = document.querySelector('.h5-navigation');
                if (h5Nav) h5Nav.style.display = 'none';
            }
            
            // 加载商品数据
            loadProductData();
        });
        
        async function loadProductData() {
            const productId = new URLSearchParams(window.location.search)
                .get('product_id');
            
            if (!productId) return;
            
            try {
                const response = await fetch(
                    `https://api.xxxx.com/products/${productId}`
                );
                const product = await response.json();
                
                renderProduct(product);
            } catch (error) {
                console.error('加载商品失败:', error);
                showError('加载失败,请重试');
            }
        }
        
        function renderProduct(product) {
            // 渲染商品信息
            document.getElementById('product-title').textContent = product.name;
            document.getElementById('product-price').textContent = 
                `¥ ${product.price}`;
            document.getElementById('product-desc').innerHTML = 
                product.description;
            
            // 渲染图片
            const gallery = document.getElementById('product-gallery');
            product.images.forEach(img => {
                const imgEl = document.createElement('img');
                imgEl.src = img.url;
                imgEl.alt = product.name;
                gallery.appendChild(imgEl);
            });
        }
    </script>
</head>
<body>
    <div class="product-container">
        <h1 id="product-title"></h1>
        <div class="price" id="product-price"></div>
        <div class="gallery" id="product-gallery"></div>
        <div class="description" id="product-desc"></div>
    </div>
</body>
</html>

总结

至此Flutter混合开发与WebView相关知识点就全部介绍完了,牢记一下核心原则:

  • 优先性能:WebView预加载、内存管理、缓存
  • 安全第一:输入验证、通信加密、权限控制

避坑指南:

常见问题及解决方案:

  1. WebView白屏
  • 原因:内存不足或初始化问题

  • 解决:实现WebView复用,添加重试机制

  1. 通信延迟高
  • 原因:频繁小消息通信

  • 解决:批量处理,二进制协议

  1. 内存泄漏
  • 原因:未正确释放WebView

  • 解决:使用WeakReference,管理生命周期

  1. 跨平台差异
  • 原因:iOS/Android WebView实现不同
  • 解决:平台适配层,功能降级

结语

混合开发不是简单的炫技,而是在原生与web之间的性能、安全、体验、效率等方面寻找一个最佳的平衡点。技术只是手段,用户体验才是目的。

如果觉得本文对你有帮助,别忘了一键三连~~~,有任何问题或想法,欢迎在评论区交流讨论! 转载请注明出处!!!

相关推荐
lancoff1 小时前
#1 onLongPressGesture
ios·swiftui
雨季6662 小时前
Flutter 智慧教育服务平台:跨端协同打造全场景教学生态
flutter
kirk_wang2 小时前
Flutter Image Editor 适配鸿蒙HarmonyOS平台实践
flutter·华为·harmonyos
_李小白3 小时前
【Android FrameWork】延伸阅读:ViewRootImpl如何管理整个view世界
android
帅气马战的账号3 小时前
开源鸿蒙+Flutter:分布式能力驱动的跨端组件化开发实战
flutter
小a彤4 小时前
Flutter 跨平台开发框架深度解析与最佳实践
flutter
Yang-Never4 小时前
Open GL ES->以指定点为中心缩放图片纹理的完整图解
android·java·开发语言·kotlin·android studio
介一安全4 小时前
【Frida Android】实战篇11:企业常用加密场景 Hook(1)
android·网络安全·逆向·安全性测试·frida
峥嵘life4 小时前
Android EDLA 认证测试内容详解
android