Flutter InAppWebView本地文件上传异常排查与验证方法
问题背景
我有一个支持上传docx、xlsx、文本文件的网页,文件会通过REST API完成格式转换,最终输出命名为<file_name> Converted.<extension>的application/octet-stream类型文件,该网页在浏览器端运行正常。
目前我尝试在Flutter应用中通过InAppWebView组件复用该网页,对应的Flutter代码如下:
import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; class MyWebView extends StatefulWidget { final String initialUrl; MyWebView({required this.initialUrl}); @override _MyWebViewState createState() => _MyWebViewState(); } class _MyWebViewState extends State<MyWebView> { late InAppWebViewController _webViewController; String? _downloadedFilePath; Future<String?> getDownloadPath() async { Directory? directory; try { if (Platform.isIOS) { directory = await getApplicationDocumentsDirectory(); } else { directory = Directory('/storage/emulated/0/Download'); if (!await directory.exists()) directory = await getExternalStorageDirectory(); } } catch (err, stack) { print("Cannot get download folder path"); } return directory?.path; } Future<bool> _requestStoragePermission() async { var status = await Permission.storage.status; if (!status.isGranted) { var result = await Permission.storage.request(); return result.isGranted; } else { return true; } } @override Widget build(BuildContext context) { return InAppWebView( initialUrlRequest: URLRequest(url: WebUri(widget.initialUrl)), initialSettings: InAppWebViewSettings( javaScriptEnabled: true, allowFileAccess: true, allowFileAccessFromFileURLs: true, allowUniversalAccessFromFileURLs: true, mediaPlaybackRequiresUserGesture: false, allowContentAccess: true, useShouldOverrideUrlLoading: true, useOnDownloadStart: true, ), shouldOverrideUrlLoading: (controller, navigationAction) async { return NavigationActionPolicy.ALLOW; }, onConsoleMessage: (controller, console) { print("FILE UPLOAD MESSAGE ${console.message}"); }, onDownloadStartRequest: (controller, url) async { print("Content disposition: ${url.contentDisposition}"); print("MIME Type: ${url.mimeType}"); print("URL Path: ${url.url.path}"); String fileName = url.suggestedFilename ?? 'downloaded_file'; String? directory = await getDownloadPath(); final filePath = '${directory}/$fileName'; print('Trying to download file to: $filePath'); try { if (await _requestStoragePermission()) { final taskId = await FlutterDownloader.enqueue( url: url.url.toString(), fileName: url.suggestedFilename, savedDir: "${directory}", showNotification: true, openFileFromNotification: true, saveInPublicStorage: true, ); _downloadedFilePath = filePath; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('File downloaded to: $filePath')), ); } } catch (e) { print('Download error: $e'); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Download failed')), ); } }, onWebViewCreated: (controller) { _webViewController = controller; }, ); } }
遇到的问题:
- 从设备本地存储上传文件时,
InAppWebView可打开文档目录并选择文件,但随后弹出警告Failed to get documents URI for content://com.android.providers.media.documents/... - 网页端用于打印文件名的
console.log未触发,怀疑文件未完成正常上传 - 已反复检查权限配置(
AndroidManifest.xml、FileProvider等),但问题依旧
验证文件是否正常上传的方法
1. 抓包分析网络请求
- 使用Android Studio的Network Inspector或第三方工具抓包,查看网页向REST API发送的上传请求:
- 检查请求是否包含
multipart/form-data格式的文件体 - 确认请求的HTTP状态码(200/201表示成功,4xx/5xx表示失败)
- 查看响应内容,确认API是否正确接收文件
- 检查请求是否包含
2. 增强网页端调试日志
在网页的文件上传逻辑中添加更详细的日志,通过Flutter的onConsoleMessage回调查看:
// 监听文件选择事件 document.querySelector('input[type="file"]').addEventListener('change', (e) => { const file = e.target.files[0]; console.log('选中文件详情:', {name: file.name, size: file.size, type: file.type}); // 监听FormData提交过程 const formData = new FormData(); formData.append('file', file); const xhr = new XMLHttpRequest(); xhr.open('POST', '/your-api-endpoint'); // 监听上传进度 xhr.upload.addEventListener('progress', (event) => { if (event.lengthComputable) { const progress = Math.round((event.loaded / event.total) * 100); console.log('上传进度:', progress + '%'); } }); // 监听上传完成/失败 xhr.addEventListener('load', () => { console.log('上传完成,响应状态:', xhr.status, '响应内容:', xhr.responseText); }); xhr.addEventListener('error', () => { console.error('上传请求失败'); }); xhr.addEventListener('abort', () => { console.error('上传请求被中断'); }); xhr.send(formData); });
3. 在Flutter层拦截上传请求
使用InAppWebView的shouldInterceptRequest回调,拦截并检查上传请求的细节:
shouldInterceptRequest: (controller, request) async { // 匹配你的API上传路径 if (request.method == 'POST' && request.url.path.contains('/upload')) { print('上传请求头: ${request.headers}'); if (request.httpBody != null) { print('请求体类型: ${request.httpBody?.contentType}'); // 若为multipart/form-data,可进一步解析内容 } } return null; // 允许请求继续发送 },
4. 检查REST API服务器日志
查看API服务器的日志,确认是否收到上传的文件,以及文件的大小、格式是否符合要求。
针对当前错误的排查建议
1. 完善FileProvider配置
在filepaths.xml中添加external-media-path,支持媒体文件URI的访问:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <cache-path name="cache" path="/" /> <files-path name="files" path="/" /> <external-path name="external" path="/" /> <external-files-path name="external_files" path="/" /> <external-cache-path name="external_cache" path="/" /> <!-- 添加媒体文件路径支持 --> <external-media-path name="external_media" path="/" /> </paths>
2. 适配Android 10+媒体权限
对于Android 10及以上版本,补充请求媒体访问权限:
Future<bool> _requestStoragePermission() async { var storageStatus = await Permission.storage.status; var mediaLocationStatus = await Permission.accessMediaLocation.status; if (!storageStatus.isGranted || !mediaLocationStatus.isGranted) { Map<Permission, PermissionStatus> statuses = await [ Permission.storage, Permission.accessMediaLocation, ].request(); return statuses[Permission.storage]!.isGranted && statuses[Permission.accessMediaLocation]!.isGranted; } else { return true; } }
3. 更新InAppWebView版本
确保使用最新版本的flutter_inappwebview,旧版本可能存在文件URI处理的bug。
内容的提问来源于stack exchange,提问作者S M Jishanul Islam
相关产品推荐
相关产品推荐

