3行代码搞定Flutter图片上传:dio+Image Picker实战指南
【免费下载链接】dio 项目地址: https://gitcode.com/gh_mirrors/dio/dio
你是否还在为Flutter应用中的图片上传功能头疼?选择图片时格式错乱、上传进度无法追踪、大文件上传频繁失败——这些问题是否让你束手无策?本文将通过dio网络库与Flutter Image Picker的完美配合,带你实现从相册选择到服务端接收的完整图片上传方案,解决90%的开发痛点。
读完本文你能得到:
- 3行代码集成图片选择功能
- 实时上传进度显示实现方案
- 异常处理与用户友好提示
- 生产环境优化配置清单
技术选型:为什么是dio+Image Picker?
在Flutter生态中,dio作为功能全面的HTTP客户端,提供了文件上传、进度监听、拦截器等核心能力,其官方文档显示已被超过10万个项目采用。而Flutter Image Picker则是社区首选的媒体选择工具,支持相册访问与相机拍摄,两者配合形成完整的图片处理闭环。
核心优势对比
| 功能 | dio+Image Picker | 原生HTTP+自定义选择 |
|---|---|---|
| 代码量 | 约50行 | 约200行 |
| 进度监听 | 内置支持 | 需要手动实现 |
| 错误处理 | 完整异常体系 | 需要自行封装 |
| 跨平台支持 | iOS/Android/Web | 需单独适配 |
环境准备与依赖配置
首先需要在pubspec.yaml中添加依赖:
dependencies:
dio: ^5.4.0
image_picker: ^1.0.7
执行安装命令:
flutter pub get
权限配置:
- Android:在
android/app/src/main/AndroidManifest.xml添加
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- iOS:在
ios/Runner/Info.plist添加
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册以选择图片</string>
完整实现步骤
1. 初始化dio实例
创建http.dart文件配置基础网络参数:
import 'package:dio/dio.dart';
final dio = Dio(BaseOptions(
baseUrl: "https://api.example.com",
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
));
// 添加日志拦截器便于调试
dio.interceptors.add(LogInterceptor(responseBody: true));
2. 实现图片选择功能
创建图片选择工具类image_picker_util.dart:
import 'package:image_picker/image_picker.dart';
class ImagePickerUtil {
static Future<String?> pickImage() async {
final picker = ImagePicker();
final pickedFile = await picker.pickImage(
source: ImageSource.gallery,
maxWidth: 800, // 压缩图片宽度
imageQuality: 80, // 图片质量
);
return pickedFile?.path;
}
}
3. 图片上传核心实现
在业务页面中集成上传功能:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'http.dart';
class UploadPage extends StatefulWidget {
@override
_UploadPageState createState() => _UploadPageState();
}
class _UploadPageState extends State<UploadPage> {
String? _imagePath;
double _progress = 0.0;
bool _isUploading = false;
Future<void> _selectImage() async {
final path = await ImagePickerUtil.pickImage();
if (path != null) {
setState(() => _imagePath = path);
}
}
Future<void> _uploadImage() async {
if (_imagePath == null) return;
setState(() {
_isUploading = true;
_progress = 0.0;
});
final formData = FormData.fromMap({
'file': await MultipartFile.fromFile(
_imagePath!,
filename: "image.jpg",
contentType: MediaType("image", "jpeg"),
),
'description': "用户头像"
});
try {
final response = await dio.post(
"/upload",
data: formData,
onSendProgress: (sent, total) {
setState(() {
_progress = sent / total;
});
},
);
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("上传成功!")),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("上传失败: ${e.toString()}")),
);
} finally {
setState(() => _isUploading = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("图片上传")),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
ElevatedButton(
onPressed: _selectImage,
child: Text("选择图片"),
),
if (_imagePath != null) ...[
SizedBox(height: 16),
Image.file(
File(_imagePath!),
height: 200,
fit: BoxFit.cover,
),
SizedBox(height: 16),
ElevatedButton(
onPressed: _isUploading ? null : _uploadImage,
child: Text("上传图片"),
),
if (_isUploading) ...[
SizedBox(height: 16),
LinearProgressIndicator(value: _progress),
SizedBox(height: 8),
Text("${(_progress * 100).toStringAsFixed(0)}%"),
]
]
],
),
),
);
}
}
高级功能实现
1. 取消上传功能
通过CancelToken实现上传任务取消:
final cancelToken = CancelToken();
// 在上传请求中添加
dio.post(
"/upload",
data: formData,
cancelToken: cancelToken,
);
// 取消按钮调用
ElevatedButton(
onPressed: () => cancelToken.cancel("用户取消上传"),
child: Text("取消"),
)
2. 多图上传实现
修改上传方法支持多文件:
Future<void> _uploadMultipleImages(List<String> paths) async {
final formData = FormData();
for (var path in paths) {
formData.files.add(MapEntry(
'files',
await MultipartFile.fromFile(path, filename: "image_${DateTime.now().millisecondsSinceEpoch}.jpg"),
));
}
// 后续上传逻辑类似单图上传
}
常见问题解决方案
1. Android图片压缩问题
通过flutter_image_compress优化压缩质量:
import 'package:flutter_image_compress/flutter_image_compress.dart';
Future<File> compressImage(String path) async {
final result = await FlutterImageCompress.compressAndGetFile(
path,
path.replaceAll(".jpg", "_compressed.jpg"),
quality: 70,
minWidth: 1024,
);
return File(result!.path);
}
2. 网络异常处理
完善异常处理逻辑:
try {
// 上传请求
} on DioException catch (e) {
if (e.type == DioExceptionType.connectionTimeout) {
showError("网络连接超时");
} else if (e.type == DioExceptionType.receiveTimeout) {
showError("服务器响应超时");
} else if (e.type == DioExceptionType.cancel) {
showError("上传已取消");
} else {
showError("上传失败: ${e.message}");
}
} catch (e) {
showError("未知错误: $e");
}
性能优化建议
- 图片预处理:上传前进行压缩,建议宽度限制在1024px以内
- 分块上传:大文件采用分块上传(参考dio分块示例)
- 缓存策略:对已上传图片进行本地缓存,避免重复上传
- 后台上传:结合
workmanager实现应用退出后继续上传
总结与最佳实践
通过dio与Image Picker的组合,我们仅用不到200行代码就实现了完整的图片上传功能,包括选择、压缩、进度显示、取消和错误处理等核心特性。在实际项目中,建议:
- 将网络请求与UI逻辑分离,采用MVVM架构
- 对所有用户输入进行验证,特别是文件类型和大小
- 添加日志收集,便于线上问题排查
- 针对不同网络环境(WiFi/移动网络)调整上传策略
掌握这套方案后,你可以轻松应对各类图片上传场景,为你的Flutter应用提供流畅可靠的媒体上传体验。
点赞收藏本文,下期我们将带来"断点续传与秒传功能实现",敬请关注!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



