You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 11:02:32