更新 AndroidManifest.xml 以启用明文流量;在 index.html 中替换 token 值

增强 WebviewPage 的清理和导航功能。
This commit is contained in:
2026-07-09 10:10:53 +08:00
parent bd5937f910
commit 98a3381716
6 changed files with 154 additions and 87 deletions
+2 -1
View File
@@ -21,7 +21,8 @@
<application <application
android:label="飞行极控录像工作台" android:label="飞行极控录像工作台"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
+1 -2
View File
@@ -32,8 +32,7 @@
window.__appInstallData = true; window.__appInstallData = true;
window.AppBridge.token = 'xxx'; window.AppBridge.token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjgxNDI4NTczNTY2NjY4ODAwLCJkYXRhIjp7fSwic3ViIjoiODE0Mjg1NzM1NjY2Njg4MDAiLCJleHAiOjE3ODM5MzM1ODQsIm5iZiI6MTc4MzMyODc4NCwiaWF0IjoxNzgzMzI4Nzg0fQ.r1wYfvpV-5HIgwUuvq1Or3jPgjc3AhfJmoV1PNP23-Y';
window.AppBridge.playId = 'xxx';
///TODO 后续业务 ///TODO 后续业务
+9 -5
View File
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:recording_tool/app/router/app_navigator.dart'; import 'package:recording_tool/app/router/app_navigator.dart';
import 'package:recording_tool/shared/widgets/app_webview.dart'; import 'package:recording_tool/features/scan_qrcode/pages/page_scan_qrcode.dart';
import 'package:recording_tool/shared/widgets/widgets.dart'; import 'package:recording_tool/shared/widgets/widgets.dart';
class AuthPageWidget extends StatefulWidget { class AuthPageWidget extends StatefulWidget {
@@ -35,10 +35,14 @@ class _AuthPageWidgetState extends State<AuthPageWidget> {
child: AppButton( child: AppButton(
label: '确定', label: '确定',
onPressed: () { onPressed: () {
// AppNavigator.push(const ScanQrCodePage()); AppNavigator.push(const ScanQrCodePage());
AppNavigator.push( // return;
const WebviewPage(assetPath: 'assets/html/index.html'), // AppNavigator.push(
); // const WebviewPage(
// url:
// 'http://192.168.1.64:5173/#/pages/h5/score-entry?eventId=10237&scheduleId=113716750536806400&playerId=74300708945531007',
// ),
// );
}, },
variant: AppButtonVariant.secondary, variant: AppButtonVariant.secondary,
), ),
@@ -4,8 +4,8 @@ import 'package:recording_tool/shared/widgets/app_bar.dart';
class PushSteamTestWidget extends StatefulWidget { class PushSteamTestWidget extends StatefulWidget {
const PushSteamTestWidget({super.key}); const PushSteamTestWidget({super.key});
final String steamKey = '20260708';
final String rtmpUrl = 'rtmp://192.168.1.245:19090/live/2026-07-07'; final String rtmpUrl = 'rtmp://192.168.1.180:19090/live/';
@override @override
State<PushSteamTestWidget> createState() => _PushSteamTestWidgetState(); State<PushSteamTestWidget> createState() => _PushSteamTestWidgetState();
@@ -37,7 +37,11 @@ class _PushSteamTestWidgetState extends State<PushSteamTestWidget>
setState(() => _isStreaming = false); setState(() => _isStreaming = false);
debugPrint('推流失败: $reason'); debugPrint('推流失败: $reason');
}, },
onDisconnection: () => setState(() => _isStreaming = false), onDisconnection: () => {
debugPrint('推流断开'),
setState(() => _isStreaming = false),
},
); );
WidgetsBinding.instance.addPostFrameCallback((_) => _initialize()); WidgetsBinding.instance.addPostFrameCallback((_) => _initialize());
} }
@@ -55,13 +59,17 @@ class _PushSteamTestWidgetState extends State<PushSteamTestWidget>
// 把服务端下发的完整 rtmp:// 地址拆成 "服务器地址" + "streamKey" 两部分 // 把服务端下发的完整 rtmp:// 地址拆成 "服务器地址" + "streamKey" 两部分
Future<void> _startPush() async { Future<void> _startPush() async {
final uri = Uri.parse(widget.rtmpUrl); // final uri = Uri.parse(widget.rtmpUrl);
final streamKey = uri.pathSegments.last; final streamKey = widget.steamKey;
final baseUrl = widget.rtmpUrl.substring( // final streamKey =
0, // '${DateTime.timestamp()}-${Random(1000).nextInt(1000).toString()}';
widget.rtmpUrl.lastIndexOf(streamKey),
); // debugPrint('streamKey- $streamKey ');
await _controller.startStreaming(streamKey: streamKey, url: baseUrl); // final baseUrl = widget.rtmpUrl.substring(
// 0,
// widget.rtmpUrl.lastIndexOf(streamKey),
// );
await _controller.startStreaming(streamKey: streamKey, url: widget.rtmpUrl);
} }
Future<void> _stopPush() => _controller.stopStreaming(); Future<void> _stopPush() => _controller.stopStreaming();
@@ -39,10 +39,12 @@ class _AuthPageWidgetState extends State<ScanQrCodePage> {
final result = await AppQrScannerDialog.show(context); final result = await AppQrScannerDialog.show(context);
if (result == null || result.isEmpty) return; if (result == null || result.isEmpty) return;
debugPrint('扫码结果: $result'); debugPrint('扫码结果: $result');
// AppNavigator.push( Future.delayed(const Duration(milliseconds: 1)).then((_) {
// const WebviewPage(url: 'https://www.dronex.cc/'), AppNavigator.push(PushSteamTestWidget());
// ); // AppNavigator.push(
AppNavigator.push(const PushSteamTestWidget()); // const WebviewPage(url: 'https://www.dronex.cc/'),
// );
});
}, },
variant: AppButtonVariant.secondary, variant: AppButtonVariant.secondary,
), ),
+118 -65
View File
@@ -1,5 +1,9 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:recording_tool/app/router/app_navigator.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart';
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
@@ -8,8 +12,15 @@ class WebviewPage extends StatefulWidget {
final String? url; final String? url;
final String? title; final String? title;
final String? assetPath; final String? assetPath;
final bool? canPop;
const WebviewPage({super.key, this.url, this.title, this.assetPath}); const WebviewPage({
super.key,
this.url,
this.title,
this.assetPath,
this.canPop,
});
@override @override
State<WebviewPage> createState() => WebviewPageState(); State<WebviewPage> createState() => WebviewPageState();
@@ -28,6 +39,16 @@ class WebviewPageState extends State<WebviewPage> {
_initController(); _initController();
} }
@override
void dispose() {
final controller = _controller;
_controller = null;
if (controller != null) {
unawaited(_disposeWebViewController(controller));
}
super.dispose();
}
Future<void> _initController() async { Future<void> _initController() async {
if ((widget.url?.isEmpty ?? true) && widget.assetPath == null) { if ((widget.url?.isEmpty ?? true) && widget.assetPath == null) {
return; return;
@@ -52,7 +73,8 @@ class WebviewPageState extends State<WebviewPage> {
}, },
onPageFinished: (String url) async { onPageFinished: (String url) async {
debugPrint('webview - page finished: $url'); debugPrint('webview - page finished: $url');
await _injectPostMessageBridge(controller); // await _injectPostMessageBridge(controller);
await _injectH5NeedData(controller);
if (!mounted) return; if (!mounted) return;
setState(() => _isLoading = false); setState(() => _isLoading = false);
}, },
@@ -107,43 +129,72 @@ class WebviewPageState extends State<WebviewPage> {
return; return;
} }
if (!mounted) return; if (!mounted) {
unawaited(_disposeWebViewController(controller));
return;
}
setState(() => _controller = controller); setState(() => _controller = controller);
} }
Future<void> _disposeWebViewController(WebViewController controller) async {
await _runWebViewCleanupStep(
'reset navigation delegate',
() => controller.setNavigationDelegate(NavigationDelegate()),
);
await _runWebViewCleanupStep(
'remove javascript channel',
() => controller.removeJavaScriptChannel(_javaScriptChannelName),
);
await _runWebViewCleanupStep(
'load blank page',
() => controller.loadRequest(Uri.parse('about:blank')),
);
await _runWebViewCleanupStep('clear cache', controller.clearCache);
await _runWebViewCleanupStep(
'clear local storage',
controller.clearLocalStorage,
);
await _runWebViewCleanupStep(
'clear cookies',
() => WebViewCookieManager().clearCookies(),
);
}
Future<void> _runWebViewCleanupStep(
String step,
Future<void> Function() cleanup,
) async {
try {
debugPrint('webview - dispose cleanup at $step ');
await cleanup();
} catch (e) {
debugPrint('webview - dispose cleanup failed at $step: $e');
}
}
void _onMessageReceived(JavaScriptMessage message) { void _onMessageReceived(JavaScriptMessage message) {
debugPrint('收到来自 WebView 的消息: ${message.message}'); debugPrint('收到来自 WebView 的消息: ${message.message}');
final jsonMap = json.decode(message.message);
if (jsonMap['type'] != null && jsonMap['type'] == 'navigator_pop') {
Navigator.of(context).maybePop();
}
} }
/// 脚本注入传输给h5 数据 /// 脚本注入传输给h5 数据
Future<void> _injectH5NeedData(WebViewController controller) async { Future<void> _injectH5NeedData(WebViewController controller) async {
final token =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjgxNDI4NTczNTY2NjY4ODAwLCJkYXRhIjp7fSwic3ViIjoiODE0Mjg1NzM1NjY2Njg4MDAiLCJleHAiOjE3ODM5MzM1ODQsIm5iZiI6MTc4MzMyODc4NCwiaWF0IjoxNzgzMzI4Nzg0fQ.r1wYfvpV-5HIgwUuvq1Or3jPgjc3AhfJmoV1PNP23-Y';
try { try {
await controller.runJavaScript(''' await controller.runJavaScript('''
(function () { (function () {
if (window.__appBridgePostMessageBridgeInstalled) { if (window.__appInstallData) {
return; return;
} }
window.__appInstallData = ${true};
window.__appBridgePostMessageBridgeInstalled = true; window.AppBridge.token = '$token';
window.addEventListener('message', function (event) {
if (!window.AppBridge || !window.AppBridge.postMessage) {
return;
}
var payload = event.data;
if (typeof payload !== 'string') {
try {
payload = JSON.stringify(payload);
if (typeof payload === 'undefined') {
payload = String(event.data);
}
} catch (error) {
payload = String(payload);
}
}
window.AppBridge.postMessage(payload);
});
})(); })();
'''); ''');
} catch (e) { } catch (e) {
@@ -151,41 +202,6 @@ class WebviewPageState extends State<WebviewPage> {
} }
} }
Future<void> _injectPostMessageBridge(WebViewController controller) async {
// try {
// await controller.runJavaScript('''
// (function () {
// if (window.__appBridgePostMessageBridgeInstalled) {
// return;
// }
// window.__appBridgePostMessageBridgeInstalled = true;
// window.addEventListener('message', function (event) {
// if (!window.AppBridge || !window.AppBridge.postMessage) {
// return;
// }
// var payload = event.data;
// if (typeof payload !== 'string') {
// try {
// payload = JSON.stringify(payload);
// if (typeof payload === 'undefined') {
// payload = String(event.data);
// }
// } catch (error) {
// payload = String(payload);
// }
// }
// window.AppBridge.postMessage(payload);
// });
// })();
// ''');
// } catch (e) {
// debugPrint('webview - inject postMessage bridge failed: $e');
// }
}
PlatformWebViewControllerCreationParams _createPlatformParams() { PlatformWebViewControllerCreationParams _createPlatformParams() {
if (WebViewPlatform.instance is WebKitWebViewPlatform) { if (WebViewPlatform.instance is WebKitWebViewPlatform) {
return WebKitWebViewControllerCreationParams( return WebKitWebViewControllerCreationParams(
@@ -226,11 +242,48 @@ class WebviewPageState extends State<WebviewPage> {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
return Stack( return PopScope(
children: [ canPop: widget.canPop ?? true,
Positioned.fill(child: WebViewWidget(controller: controller)), onPopInvokedWithResult: (didPop, result) async {
if (_isLoading) const Center(child: CircularProgressIndicator()), if (didPop) return;
],
final ok = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text("提示"),
content: const Text("是否返回上一页?"),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text("取消"),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text("确定"),
),
],
),
);
if (ok == true) {
AppNavigator.pop();
}
},
child: Scaffold(
body: Column(
children: [
SizedBox(height: 48),
Expanded(
child: Stack(
children: [
Positioned.fill(child: WebViewWidget(controller: controller)),
if (_isLoading)
const Center(child: CircularProgressIndicator()),
],
),
),
],
),
),
); );
} }
} }