Flutter WebView 在 OpenHarmony 上的企业级网页集成方案
本文介绍了Flutter WebView在OpenHarmony平台上的企业级集成方案。文章从技术架构设计入手,提出了分层架构模型,包括UI层、控制层、通信层、业务层和安全层,并强调了安全性、通信稳定性等核心设计原则。详细说明了项目配置与依赖管理,包括必要的pubspec.yaml配置和项目目录结构规划。最后展示了核心组件的实现,包括常量定义和异常处理机制,为开发者提供了完整的WebView集成技
Flutter WebView 在 OpenHarmony 上的企业级网页集成方案
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
一、引言
WebView 是移动应用中常用的组件,用于在原生应用中集成网页内容。在现代应用开发中,WebView 不仅用于展示静态网页,还广泛应用于混合开发、H5 支付、在线客服、活动页面等场景。
本文将详细介绍如何在 Flutter-OH 项目中构建一套企业级的 WebView 集成方案,涵盖从基础配置、架构设计、JS 通信、安全机制,到 OpenHarmony 平台深度适配的完整技术体系。
二、WebView 技术架构设计
2.1 分层架构设计
我们采用分层架构设计,将 WebView 系统分为以下几个层次:
┌─────────────────────────────────────────┐
│ UI 层 (WebView Widgets) │
├─────────────────────────────────────────┤
│ 控制层 (WebViewController) │
├─────────────────────────────────────────┤
│ 通信层 (JSBridge) │
├─────────────────────────────────────────┤
│ 业务层 (WebView Business Logic) │
├─────────────────────────────────────────┤
│ 安全层 (Security Manager) │
└─────────────────────────────────────────┘
2.2 核心设计原则
- 安全性优先:完善的安全机制,防止 XSS 攻击
- 通信稳定:可靠的 JS-Native 双向通信
- 性能优化:优化 WebView 渲染性能
- 可扩展性:易于添加新的 JS 接口
- 鸿蒙化适配:针对 OpenHarmony 平台进行深度优化
三、项目配置与依赖
3.1 依赖配置
在 pubspec.yaml 中添加必要的依赖:
name: flutter_webview_harmony
description: Flutter for OpenHarmony WebView 集成方案
version: 1.0.0
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
webview_flutter: ^4.4.0
webview_flutter_android: ^3.12.0
webview_flutter_wkwebview: ^3.9.4
url_launcher: ^6.2.2
shared_preferences: ^2.2.2
crypto: ^3.0.3
path_provider: ^2.1.1
connectivity_plus: ^5.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
mocktail: ^1.0.3
3.2 项目目录结构
lib/
├── core/
│ ├── constants/
│ │ └── webview_constants.dart
│ ├── services/
│ │ ├── webview_service.dart
│ │ └── js_bridge_service.dart
│ ├── utils/
│ │ ├── webview_cache_manager.dart
│ │ └── webview_cookie_manager.dart
│ └── exceptions/
│ └── webview_exceptions.dart
├── features/
│ └── webview/
│ ├── widgets/
│ │ ├── harmony_webview.dart
│ │ └── webview_navigation_bar.dart
│ ├── controllers/
│ │ └── harmony_webview_controller.dart
│ └── pages/
│ └── webview_page.dart
└── main.dart
四、核心组件实现
4.1 常量定义
创建 lib/core/constants/webview_constants.dart:
class WebViewConstants {
static const String defaultUserAgent =
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1';
static const String jsBridgeName = 'FlutterBridge';
static const Duration defaultTimeout = Duration(seconds: 30);
static const int defaultCacheSize = 50 * 1024 * 1024; // 50MB
static const List<String> allowedSchemes = [
'http',
'https',
'file',
];
static const List<String> safeDomains = [
'your-domain.com',
'your-cdn.com',
];
static const List<String> blockedUrls = [
'javascript:',
'blob:',
];
}
class JsBridgeMethods {
static const String getAppVersion = 'getAppVersion';
static const String setTitle = 'setTitle';
static const String showToast = 'showToast';
static const String navigate = 'navigate';
static const String getLocation = 'getLocation';
static const String takePhoto = 'takePhoto';
static const String chooseImage = 'chooseImage';
static const String share = 'share';
static const String pay = 'pay';
static const String openNewWindow = 'openNewWindow';
static const String closeWindow = 'closeWindow';
static const String setStorage = 'setStorage';
static const String getStorage = 'getStorage';
static const String removeStorage = 'removeStorage';
static const String clearStorage = 'clearStorage';
}
class WebViewErrorCodes {
static const int unknown = -1;
static const int hostLookup = 1;
static const int connectionFailed = 2;
static const int timeout = 3;
static const int tooManyRequests = 4;
static const int httpError = 5;
static const int securityError = 6;
}
4.2 异常定义
创建 lib/core/exceptions/webview_exceptions.dart:
class WebViewException implements Exception {
final String message;
final int? code;
final Object? cause;
final StackTrace? stackTrace;
WebViewException(
this.message, {
this.code,
this.cause,
this.stackTrace,
});
String toString() {
final buffer = StringBuffer('WebViewException: $message');
if (code != null) {
buffer.writeln('Code: $code');
}
if (cause != null) {
buffer.writeln('Cause: $cause');
}
if (stackTrace != null) {
buffer.writeln('StackTrace: $stackTrace');
}
return buffer.toString();
}
}
class JsBridgeException extends WebViewException {
final String? method;
final Map<String, dynamic>? params;
JsBridgeException(
String message, {
this.method,
this.params,
int? code,
Object? cause,
StackTrace? stackTrace,
}) : super(
message,
code: code,
cause: cause,
stackTrace: stackTrace,
);
}
class SecurityException extends WebViewException {
final String? url;
final String? reason;
SecurityException(
String message, {
this.url,
this.reason,
int? code,
Object? cause,
StackTrace? stackTrace,
}) : super(
message,
code: code,
cause: cause,
stackTrace: stackTrace,
);
}
class NavigationBlockedException extends WebViewException {
final String blockedUrl;
final String? reason;
NavigationBlockedException(
this.blockedUrl, {
this.reason,
int? code,
Object? cause,
StackTrace? stackTrace,
}) : super(
'Navigation blocked: $blockedUrl',
code: code,
cause: cause,
stackTrace: stackTrace,
);
}
4.3 JS Bridge 核心实现
创建 lib/core/services/js_bridge_service.dart:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import '../constants/webview_constants.dart';
import '../exceptions/webview_exceptions.dart';
class JsBridgeService {
static final JsBridgeService _instance = JsBridgeService._internal();
static JsBridgeService get instance => _instance;
JsBridgeService._internal();
final Map<String, Function> _handlers = {};
WebViewController? _controller;
bool _isInitialized = false;
Future<void> initialize(WebViewController controller) async {
if (_isInitialized) return;
_controller = controller;
_registerDefaultHandlers();
_isInitialized = true;
}
void dispose() {
_controller = null;
_handlers.clear();
_isInitialized = false;
}
void _registerDefaultHandlers() {
_handlers[JsBridgeMethods.getAppVersion] = _handleGetAppVersion;
_handlers[JsBridgeMethods.setTitle] = _handleSetTitle;
_handlers[JsBridgeMethods.showToast] = _handleShowToast;
_handlers[JsBridgeMethods.navigate] = _handleNavigate;
_handlers[JsBridgeMethods.openNewWindow] = _handleOpenNewWindow;
_handlers[JsBridgeMethods.closeWindow] = _handleCloseWindow;
_handlers[JsBridgeMethods.setStorage] = _handleSetStorage;
_handlers[JsBridgeMethods.getStorage] = _handleGetStorage;
_handlers[JsBridgeMethods.removeStorage] = _handleRemoveStorage;
_handlers[JsBridgeMethods.clearStorage] = _handleClearStorage;
}
void registerHandler(String method, Function handler) {
_handlers[method] = handler;
}
void unregisterHandler(String method) {
_handlers.remove(method);
}
void handleMessage(JavaScriptMessage message) {
try {
final data = jsonDecode(message.message);
final callbackId = data['callbackId'];
final method = data['method'];
final params = data['params'];
final handler = _handlers[method];
if (handler == null) {
_sendError(callbackId, 'Method not found: $method');
return;
}
_executeHandler(callbackId, method, params, handler);
} catch (e, stackTrace) {
if (e is FormatException) {
debugPrint('Invalid JSON format: ${message.message}');
} else {
debugPrint('Error handling JS message: $e');
}
}
}
Future<void> _executeHandler(
String? callbackId,
String method,
dynamic params,
Function handler,
) async {
try {
final result = await _callHandler(handler, params);
_sendSuccess(callbackId, result);
} catch (e, stackTrace) {
_sendError(
callbackId,
'Error executing $method: $e',
JsBridgeException(
'Error executing method',
method: method,
params: params,
cause: e,
stackTrace: stackTrace,
),
);
}
}
Future<dynamic> _callHandler(Function handler, dynamic params) async {
if (params is Map) {
return await handler(params);
} else {
return await handler(params);
}
}
void _sendSuccess(String? callbackId, dynamic data) {
if (callbackId == null || _controller == null) return;
final result = jsonEncode({
'code': 0,
'data': data,
});
_executeJs('window.$callbackId.success($result)');
}
void _sendError(String? callbackId, String message, [Exception? error]) {
if (callbackId == null || _controller == null) return;
final result = jsonEncode({
'code': -1,
'message': message,
});
_executeJs('window.$callbackId.error($result)');
}
Future<dynamic> _executeJs(String script) async {
if (_controller == null) {
throw WebViewException('WebView not initialized');
}
return await _controller!.runJavaScriptReturningResult(script);
}
Future<dynamic> invokeMethod(
String method, {
Map<String, dynamic>? params,
}) async {
final script = '''
(function() {
var method = ${jsonEncode(method)};
var params = ${jsonEncode(params ?? {})};
if (window.$method && typeof window.$method === 'function') {
return JSON.stringify({
code: 0,
data: window.$method(params)
});
} else {
return JSON.stringify({
code: -1,
message: 'Method not found'
});
}
})();
''';
final result = await _executeJs(script);
final jsonResult = jsonDecode(result.toString());
if (jsonResult['code'] != 0) {
throw JsBridgeException(
jsonResult['message'] ?? 'Error invoking JS method',
method: method,
params: params,
);
}
return jsonResult['data'];
}
Map<String, String> _getHeaders() {
return {
'User-Agent': WebViewConstants.defaultUserAgent,
'X-Requested-With': 'FlutterWebView',
};
}
bool _isUrlAllowed(String url) {
try {
final uri = Uri.parse(url);
if (!WebViewConstants.allowedSchemes.contains(uri.scheme)) {
return false;
}
final host = uri.host;
for (final domain in WebViewConstants.safeDomains) {
if (host.endsWith(domain)) {
return true;
}
}
return false;
} catch (e) {
return false;
}
}
Future<Map<String, dynamic>> _handleGetAppVersion(
Map<String, dynamic> params,
) async {
return {
'version': '1.0.0',
'platform': 'OpenHarmony',
'buildNumber': '100',
};
}
Future<void> _handleSetTitle(Map<String, dynamic> params) async {
final title = params['title'] as String?;
if (title != null) {
debugPrint('Setting title: $title');
}
}
Future<void> _handleShowToast(Map<String, dynamic> params) async {
final message = params['message'] as String?;
final duration = params['duration'] as String?;
if (message != null) {
debugPrint('Toast: $message');
}
}
Future<void> _handleNavigate(Map<String, dynamic> params) async {
final url = params['url'] as String?;
if (url != null && _controller != null) {
await _controller!.loadRequest(Uri.parse(url));
}
}
Future<void> _handleOpenNewWindow(Map<String, dynamic> params) async {
final url = params['url'] as String?;
if (url != null) {
debugPrint('Opening new window: $url');
}
}
Future<void> _handleCloseWindow(Map<String, dynamic> params) async {
debugPrint('Closing window');
}
Future<void> _handleSetStorage(Map<String, dynamic> params) async {
final key = params['key'] as String?;
final value = params['value'];
if (key != null && value != null) {
debugPrint('Setting storage: $key = $value');
}
}
Future<dynamic> _handleGetStorage(Map<String, dynamic> params) async {
final key = params['key'] as String?;
return key != null ? {} : null;
}
Future<void> _handleRemoveStorage(Map<String, dynamic> params) async {
final key = params['key'] as String?;
if (key != null) {
debugPrint('Removing storage: $key');
}
}
Future<void> _handleClearStorage(Map<String, dynamic> params) async {
debugPrint('Clearing storage');
}
}
4.4 WebView 服务实现
创建 lib/core/services/webview_service.dart:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import '../constants/webview_constants.dart';
import '../exceptions/webview_exceptions.dart';
import 'js_bridge_service.dart';
class WebViewService {
static final WebViewService _instance = WebViewService._internal();
static WebViewService get instance => _instance;
WebViewService._internal();
WebViewController? _controller;
final JsBridgeService _jsBridge = JsBridgeService.instance;
bool _isLoading = false;
String? _currentUrl;
Future<void> initialize(WebViewController controller) async {
_controller = controller;
await _configureWebView();
await _jsBridge.initialize(controller);
}
Future<void> _configureWebView() async {
await _controller!.setJavaScriptMode(JavaScriptMode.unrestricted);
await _controller!.setNavigationDelegate(
NavigationDelegate(
onProgress: _onProgress,
onPageStarted: _onPageStarted,
onPageFinished: _onPageFinished,
onWebResourceError: _onWebResourceError,
onNavigationRequest: _onNavigationRequest,
onUrlChange: _onUrlChange,
),
);
await _controller!.addJavaScriptChannel(
WebViewConstants.jsBridgeName,
onMessageReceived: _jsBridge.handleMessage,
);
await _controller!.setUserAgent(WebViewConstants.defaultUserAgent);
}
void _onProgress(int progress) {
debugPrint('WebView progress: $progress%');
}
Future<void> _onPageStarted(String url) async {
_isLoading = true;
_currentUrl = url;
debugPrint('Page started: $url');
}
Future<void> _onPageFinished(String url) async {
_isLoading = false;
_currentUrl = url;
debugPrint('Page finished: $url');
await _injectJavascript();
}
Future<void> _injectJavascript() async {
const injectionScript = '''
(function() {
if (window.__flutterBridgeInjected__) {
return;
}
window.__flutterBridgeInjected__ = true;
window.$WebViewConstants.jsBridgeName = Object.assign(
window.$WebViewConstants.jsBridgeName || {},
{
invoke: function(method, params) {
return new Promise(function(resolve, reject) {
var callbackId = 'callback_' + Date.now() + '_' + Math.random();
window[callbackId] = {
success: function(result) {
try {
var data = JSON.parse(result);
if (data.code === 0) {
resolve(data.data);
} else {
reject(new Error(data.message));
}
} catch (e) {
reject(e);
} finally {
delete window[callbackId];
}
},
error: function(error) {
try {
var err = JSON.parse(error);
reject(new Error(err.message));
} catch (e) {
reject(error);
} finally {
delete window[callbackId];
}
}
};
var message = JSON.stringify({
callbackId: callbackId,
method: method,
params: params
});
try {
window.$WebViewConstants.jsBridgeName.postMessage(message);
} catch (e) {
reject(e);
delete window[callbackId];
}
});
}
}
);
})();
''';
await _controller!.runJavaScript(injectionScript);
}
void _onWebResourceError(WebResourceError error) {
debugPrint('WebResourceError: ${error.errorCode} - ${error.description}');
}
Future<NavigationDecision> _onNavigationRequest(
NavigationRequest request,
) async {
final url = request.url;
if (_shouldOpenExternal(url)) {
await launchUrl(Uri.parse(url));
return NavigationDecision.prevent;
}
if (!_isNavigationAllowed(url)) {
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
}
bool _shouldOpenExternal(String url) {
final uri = Uri.parse(url);
if (['tel', 'mailto', 'sms', 'geo', 'market'].contains(uri.scheme)) {
return true;
}
return false;
}
bool _isNavigationAllowed(String url) {
try {
final uri = Uri.parse(url);
for (final blockedUrl in WebViewConstants.blockedUrls) {
if (url.startsWith(blockedUrl)) {
return false;
}
}
return true;
} catch (e) {
return false;
}
}
void _onUrlChange(UrlChange change) {
_currentUrl = change.url;
debugPrint('URL changed: ${change.url}');
}
Future<void> loadUrl(String url, {Map<String, String>? headers}) async {
if (_controller == null) {
throw WebViewException('WebView not initialized');
}
await _controller!.loadRequest(
Uri.parse(url),
headers: headers ?? {},
);
}
Future<void> reload() async {
if (_controller == null) {
throw WebViewException('WebView not initialized');
}
await _controller!.reload();
}
Future<void> goBack() async {
if (_controller == null) {
throw WebViewException('WebView not initialized');
}
if (await _controller!.canGoBack()) {
await _controller!.goBack();
}
}
Future<void> goForward() async {
if (_controller == null) {
throw WebViewException('WebView not initialized');
}
if (await _controller!.canGoForward()) {
await _controller!.goForward();
}
}
Future<void> clearCache() async {
if (_controller == null) {
throw WebViewException('WebView not initialized');
}
await _controller!.clearCache();
await _controller!.clearLocalStorage();
}
Future<void> clearCookies() async {
final cookieManager = WebViewCookieManager();
await cookieManager.clearCookies();
}
Future<String?> getCurrentUrl() async {
return _currentUrl;
}
bool get isLoading => _isLoading;
void dispose() {
_controller = null;
_jsBridge.dispose();
}
}
五、鸿蒙化适配与实战案例
5.1 鸿蒙平台权限配置
在 module.json5 中添加必要的权限:
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone",
"tablet"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.READ_MEDIA"
},
{
"name": "ohos.permission.CAMERA"
},
{
"name": "ohos.permission.MICROPHONE"
},
{
"name": "ohos.permission.LOCATION"
}
]
}
}
5.2 Harmony WebView Widget 实现
创建 lib/features/webview/widgets/harmony_webview.dart:
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import '../../../core/services/webview_service.dart';
import '../../../core/constants/webview_constants.dart';
class HarmonyWebView extends StatefulWidget {
final String initialUrl;
final String? title;
final bool showNavigationBar;
final bool showProgressBar;
final bool enableJavaScript;
final Map<String, String>? headers;
final Function(String)? onUrlChanged;
final Function(Map<String, dynamic>)? onJsMessage;
const HarmonyWebView({
super.key,
required this.initialUrl,
this.title,
this.showNavigationBar = true,
this.showProgressBar = true,
this.enableJavaScript = true,
this.headers,
this.onUrlChanged,
this.onJsMessage,
});
State<HarmonyWebView> createState() => _HarmonyWebViewState();
}
class _HarmonyWebViewState extends State<HarmonyWebView> {
late final WebViewController _controller;
final WebViewService _webViewService = WebViewService.instance;
final ValueNotifier<bool> _isLoading = ValueNotifier(true);
final ValueNotifier<int> _progress = ValueNotifier(0);
final ValueNotifier<bool> _canGoBack = ValueNotifier(false);
final ValueNotifier<bool> _canGoForward = ValueNotifier(false);
String? _currentTitle;
void initState() {
super.initState();
_initWebView();
}
Future<void> _initWebView() async {
_controller = WebViewController();
await _controller.setJavaScriptMode(JavaScriptMode.unrestricted);
await _controller.setNavigationDelegate(
NavigationDelegate(
onProgress: (progress) {
_progress.value = progress;
},
onPageStarted: (url) {
_isLoading.value = true;
widget.onUrlChanged?.call(url);
},
onPageFinished: (url) async {
_isLoading.value = false;
await _updateNavigationState();
await _updateTitle();
},
onWebResourceError: (error) {
_isLoading.value = false;
},
onNavigationRequest: (request) async {
if (_shouldOpenExternal(request.url)) {
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
onUrlChange: (change) {
widget.onUrlChanged?.call(change.url ?? '');
},
),
);
await _webViewService.initialize(_controller);
if (widget.headers != null && widget.headers!.isNotEmpty) {
await _controller.loadRequest(
Uri.parse(widget.initialUrl),
headers: widget.headers,
);
} else {
await _controller.loadRequest(Uri.parse(widget.initialUrl));
}
}
bool _shouldOpenExternal(String url) {
final uri = Uri.parse(url);
return ['tel', 'mailto', 'sms'].contains(uri.scheme);
}
Future<void> _updateNavigationState() async {
_canGoBack.value = await _controller.canGoBack();
_canGoForward.value = await _controller.canGoForward();
}
Future<void> _updateTitle() async {
final title = await _controller.getTitle();
if (mounted) {
setState(() {
_currentTitle = title;
});
}
}
void dispose() {
_webViewService.dispose();
_isLoading.dispose();
_progress.dispose();
_canGoBack.dispose();
_canGoForward.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: widget.showNavigationBar
? AppBar(
title: Text(widget.title ?? _currentTitle ?? 'WebView'),
actions: [
ValueListenableBuilder<bool>(
valueListenable: _canGoBack,
builder: (context, canGoBack, child) {
return IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: canGoBack ? () => _controller.goBack() : null,
);
},
),
ValueListenableBuilder<bool>(
valueListenable: _canGoForward,
builder: (context, canGoForward, child) {
return IconButton(
icon: const Icon(Icons.arrow_forward),
onPressed: canGoForward ? () => _controller.goForward() : null,
);
},
),
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => _controller.reload(),
),
],
)
: null,
body: Column(
children: [
if (widget.showProgressBar)
ValueListenableBuilder<int>(
valueListenable: _progress,
builder: (context, progress, child) {
if (progress == 0 || progress == 100) {
return const SizedBox.shrink();
}
return LinearProgressIndicator(value: progress / 100);
},
),
Expanded(
child: WebViewWidget(controller: _controller),
),
],
),
);
}
}
5.3 H5 支付集成实战
创建 H5 支付集成方案:
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class PaymentWebView extends StatefulWidget {
final String paymentUrl;
final String? callbackUrl;
final Function(bool)? onPaymentResult;
const PaymentWebView({
super.key,
required this.paymentUrl,
this.callbackUrl,
this.onPaymentResult,
});
State<PaymentWebView> createState() => _PaymentWebViewState();
}
class _PaymentWebViewState extends State<PaymentWebView> {
late final WebViewController _controller;
void initState() {
super.initState();
_initWebView();
}
Future<void> _initWebView() async {
_controller = WebViewController();
await _controller.setJavaScriptMode(JavaScriptMode.unrestricted);
await _controller.setNavigationDelegate(
NavigationDelegate(
onProgress: (progress) => debugPrint('Payment progress: $progress%'),
onPageStarted: (url) => debugPrint('Payment page started: $url'),
onPageFinished: (url) => debugPrint('Payment page finished: $url'),
onNavigationRequest: (request) {
if (widget.callbackUrl != null &&
request.url.startsWith(widget.callbackUrl!)) {
final uri = Uri.parse(request.url);
final success = uri.queryParameters['success'] == 'true';
widget.onPaymentResult?.call(success);
return NavigationDecision.prevent;
}
if (request.url.startsWith('weixin://') ||
request.url.startsWith('alipay://') ||
request.url.startsWith('alipays://')) {
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
);
await _controller.loadRequest(Uri.parse(widget.paymentUrl));
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('支付')),
body: WebViewWidget(controller: _controller),
);
}
}
六、安全机制与性能优化
6.1 安全防护措施
- XSS 防护:对 JS 通信进行安全验证
- URL 白名单:只允许访问受信任的域名
- Cookie 管理:安全处理 cookies
- 内容安全策略:使用 CSP 限制资源加载
- https 强制:默认只允许 https 访问
6.2 性能优化策略
- 缓存优化:合理配置缓存策略
- 资源预加载:提前加载常用资源
- 图片优化:使用 WebP 格式和懒加载
- JS 优化:减少 JS 执行阻塞
- DOM 优化:优化 DOM 操作
七、测试策略
7.1 单元测试
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import '../lib/core/services/js_bridge_service.dart';
import '../lib/core/exceptions/webview_exceptions.dart';
class MockWebViewController extends Mock implements WebViewController {}
void main() {
group('JsBridgeService', () {
late JsBridgeService jsBridge;
late MockWebViewController mockController;
setUp(() {
jsBridge = JsBridgeService();
mockController = MockWebViewController();
});
test('should initialize successfully', () async {
await expectLater(
jsBridge.initialize(mockController),
completes,
);
});
test('should register and invoke handler', () async {
await jsBridge.initialize(mockController);
var invoked = false;
jsBridge.registerHandler('testMethod', (params) async {
invoked = true;
return {'success': true};
});
expect(invoked, false);
});
});
}
八、总结
本文详细介绍了 Flutter WebView 在 OpenHarmony 上的企业级网页集成方案。通过完善的架构设计、安全的 JS 通信机制和深度的平台适配,可以在 OpenHarmony 平台上构建稳定、安全、高性能的 WebView 应用。
核心要点:
- 使用 webview_flutter 实现 WebView 组件
- 构建可靠的 JSBridge 双向通信机制
- 针对 OpenHarmony 平台进行权限配置
- 实现完善的安全防护机制
- 提供 H5 支付等实战场景方案
- 优化 WebView 性能和用户体验
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)