坑: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 rootBundle
Future<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 operations
PermissionStatus 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 assets
await 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();
// 前端路径默认dist
final 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 directory
final webDir = Directory(path)..createSync(recursive: true);
// List of files to copy
final 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:8080
try {
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');
}