Flutter中如何从字节存储图片并实现缓存读取?
Flutter 图片本地缓存实现方案
核心思路
通过唯一标识生成缓存文件名,结合本地文件系统实现图片的存储与读取,请求前先检查本地缓存,存在则直接读取,不存在再发起API请求。
步骤实现
1. 添加依赖
在pubspec.yaml中引入必要的包:
dependencies: flutter: sdk: flutter path_provider: ^2.1.2 # 获取本地存储路径 permission_handler: ^11.0.1 # 处理存储权限(Android/iOS) crypto: ^3.0.3 # 生成哈希值作为缓存键
2. 生成唯一缓存键
用图片的请求URL(或接口参数组合)生成MD5哈希值,确保每个图片对应唯一的缓存文件名:
import 'package:crypto/crypto.dart'; import 'dart:convert'; String generateCacheKey(String imageUrl) { var bytes = utf8.encode(imageUrl); var digest = md5.convert(bytes); return digest.toString(); // 返回32位MD5字符串作为文件名 }
3. 获取本地存储路径
选择应用专属的缓存目录(临时目录系统可能自动清理,需永久存储可改用文档目录):
import 'package:path_provider/path_provider.dart'; import 'dart:io'; Future<String> getCacheDirPath() async { final directory = await getTemporaryDirectory(); // 临时缓存目录 // 永久存储用:await getApplicationDocumentsDirectory() return directory.path; }
4. 缓存检查与读取
请求图片前先判断本地是否存在有效缓存文件:
Future<Uint8List?> getCachedImage(String cacheKey) async { final cacheDir = await getCacheDirPath(); final file = File('$cacheDir/$cacheKey.png'); // 根据图片格式修改后缀 if (await file.exists() && await file.length() > 0) { return await file.readAsBytes(); } return null; }
5. 存储图片到本地
API请求成功后,将Uint8List写入本地文件:
Future<void> saveImageToCache(String cacheKey, Uint8List imageBytes) async { final cacheDir = await getCacheDirPath(); final file = File('$cacheDir/$cacheKey.png'); await file.writeAsBytes(imageBytes); }
6. 完整业务逻辑示例
import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; class ImageCacheDemo extends StatefulWidget { final String imageUrl; const ImageCacheDemo({super.key, required this.imageUrl}); @override State<ImageCacheDemo> createState() => _ImageCacheDemoState(); } class _ImageCacheDemoState extends State<ImageCacheDemo> { Uint8List? _imageBytes; bool _isLoading = false; @override void initState() { super.initState(); _loadImage(); } Future<void> _loadImage() async { setState(() => _isLoading = true); final cacheKey = generateCacheKey(widget.imageUrl); // 优先读取缓存 final cachedBytes = await getCachedImage(cacheKey); if (cachedBytes != null) { setState(() { _imageBytes = cachedBytes; _isLoading = false; }); return; } // 缓存不存在,发起API请求 try { final response = await http.get(Uri.parse(widget.imageUrl)); if (response.statusCode == 200) { final bytes = response.bodyBytes; setState(() => _imageBytes = bytes); // 写入缓存 await saveImageToCache(cacheKey, bytes); } } catch (e) { print('图片加载失败: $e'); } finally { setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { if (_isLoading) { return const Center(child: CircularProgressIndicator()); } return _imageBytes != null ? Image.memory(_imageBytes!) : const Center(child: Text('图片加载失败')); } }
7. 权限配置(重要)
- Android:在
android/app/src/main/AndroidManifest.xml中添加:<!-- 临时缓存目录无需WRITE权限,用文档目录则需添加对应权限 --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> - iOS:在
ios/Runner/Info.plist中添加:<key>NSPhotoLibraryUsageDescription</key> <string>需要访问相册存储图片缓存</string>
扩展优化
- 缓存清理:定期清理超过指定天数的缓存文件,避免占用过多存储空间
- 缓存大小限制:设置总缓存大小阈值,超过时删除最旧的缓存文件
- 错误重试:请求失败时添加重试逻辑,提升容错性
- 二级缓存:结合内存缓存(如
MemoryImage)实现内存+磁盘的二级缓存,加快加载速度
内容的提问来源于stack exchange,提问作者Jiehfeng
相关产品推荐
相关产品推荐

