坑:shelf_static 的createStaticHandler 一直报错
throw ArgumentError('A directory corresponding to fileSystemPath ''"$fileSystemPath" could not be found');
解决思路
getApplicationDocumentsDirectory 获取当前目录,并创建文件夹
通过读AssetManifest.json 文件获取静态资源,然后写进去,当静态资源目录
final manifestContent = await rootBundle.loadString('AssetManifest.json');
下面是完整代码
import 'dart:developer';
import 'dart:io';
import 'package:permission_handler/permission_handler.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'dart:convert';
import 'package:shelf_router/shelf_router.dart' as shelf_router;
import 'package:shelf_static/shelf_static.dart';
import 'package:flutter/services.dart'; // For rootBundleFuture<void> requestPermissions() async {if (Platform.isAndroid) {if ((await _isAndroidVersion28OrAbove())) {PermissionStatus storageStatus = await Permission.storage.request();if (storageStatus.isDenied || storageStatus.isPermanentlyDenied) {log('Storage permission denied');openAppSettings();}}PermissionStatus manageExternalStorageStatus =await Permission.manageExternalStorage.request();if (manageExternalStorageStatus.isDenied ||manageExternalStorageStatus.isPermanentlyDenied) {log("Manage ExternalStorage permission denied");}// Request internet permissions (Note: INTERNET permission is typically granted by default on Android)// Here we're demonstrating ACCESS_NETWORK_STATE which can be useful for network operationsPermissionStatus networkStatus =await Permission.accessMediaLocation.request();if (networkStatus.isDenied || networkStatus.isPermanentlyDenied) {log('Network permission denied');}PermissionStatus cameraStatus = await Permission.camera.request();if (cameraStatus.isDenied || cameraStatus.isPermanentlyDenied) {log('Camera permission denied');}PermissionStatus microphoneStatus = await Permission.microphone.request();if (microphoneStatus.isDenied || microphoneStatus.isPermanentlyDenied) {log('Microphone permission denied');}}
}Future<void> startWebServer(Directory assetsDirectory) async {final staticHandler =createStaticHandler(assetsDirectory.path, defaultDocument: 'index.html');final router = shelf_router.Router()..all('/<ignored|.*>', staticHandler);final server = await io.serve(const Pipeline().addMiddleware(logRequests()).addHandler(router.call),InternetAddress.loopbackIPv4,8080,);log('Serving at http://${server.address.host}:${server.port}');
}Future<void> copyAssetsToLocal(String webRootBundlePrefix, Directory appDir) async {if (!await appDir.exists()) {await appDir.create(recursive: true);}// Copy all assetsawait copyAssetDirectory(webRootBundlePrefix, appDir.path);
}Future<void> copyAssetDirectory(String assetPath, String localPath) async {final manifestContent = await rootBundle.loadString('AssetManifest.json');final Map<String, dynamic> manifestMap = json.decode(manifestContent);final List<String> assetPaths = manifestMap.keys.where((String key) => key.startsWith(assetPath)).toList();await deleteDirectory(localPath);for (String asset in assetPaths) {final filePath = asset.replaceFirst(assetPath, localPath);final file = File(filePath);final ByteData data = await rootBundle.load(asset);final List<int> bytes =data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);if (!await file.parent.exists()) {await file.parent.create(recursive: true);}await file.writeAsBytes(bytes);}
}Future<void> deleteDirectory(String path) async {final directory = Directory(path);if (await directory.exists()) {try {await directory.delete(recursive: true);} catch (e) {log('Failed to delete directory: $e');}} else {log('Directory does not exist: $path');}
}Future<bool> _isAndroidVersion28OrAbove() async {// 检查是否是Android 28或更高版本int sdkVersion = 0;try {sdkVersion = int.parse(await _getAndroidSdkVersion());} catch (e) {log("Failed to get Android SDK version: $e");}return sdkVersion >= 28;
}Future<String> _getAndroidSdkVersion() async {return Platform.isAndroid? (await Permission.storage.request().isGranted)? "28": "0": "0";
}
调用(在webview启动之前)
// 不申请权限也可以启动await requestPermissions();final appRoot = await getApplicationDocumentsDirectory();// 前端路径默认distfinal appDir = Directory('${appRoot.path}/$webDistDir');await copyAssetsToLocal("packages/face_liveness_web/$webDistDir", appDir);startWebServer(appDir);
文件目录
看到开源项目的写法(使用未验证),项目目录有assets/web
final dir = await getTemporaryDirectory();final path = p.join(dir.path, 'web');// Create the target directoryfinal webDir = Directory(path)..createSync(recursive: true);// List of files to copyfinal files = ['index.html', 'full.json', 'main.css', 'main.js'];for (var filename in files) {final ByteData data = await rootBundle.load('assets/web/$filename');final file = File(p.join(webDir.path, filename));await file.writeAsBytes(data.buffer.asUint8List());}final handler = createStaticHandler(webDir.path,defaultDocument: 'index.html',serveFilesOutsidePath: true,);// Start server on localhost:8080try {final server = await shelf_io.serve(handler, 'localhost', 8080);print('Serving at http://${server.address.host}:${server.port}');} catch (e) {print('Failed to start server: $e');}