当前位置: 首页 > news >正文

Flutter中的网络请求图片存储为缓存,与定制删除本地缓存

Flutter中的网络请求图片存储为缓存,与定制删除本地缓存
1:封装请求图片函数
2:访问的图片都会转为本地缓存,当相同的请求url,会在本地调用图片
3:本地缓存管理【windows与andriod已经测试】【有页面】【有调用案例】
4:删除本地缓存

清理缓存页面(下方代码中已包括)
在这里插入图片描述

在这里插入图片描述

windows中显示图片-----------安卓中显示图片
这里还没有进行优化图片显示的宽高,圆角,请自行设置
在这里插入图片描述

打印日志(显示图片请求的获取过程与报错原因)

在这里插入图片描述

TuPianJiaZai 图片加载工具使用教程

注意事项

  1. imageUrl 可以为 null,此时会显示空白
  2. 图片会自动缓存到本地
  3. 支持自动重试3次
  4. 默认有加载动画和错误提示
  5. 支持所有标准图片格式

实际应用场景

  1. 商品展示卡片
  2. 用户头像
  3. 图片列表
  4. 背景图片
  5. Banner图片

1. 基本用法

1.1导入文件

import '../utils/get_images/tupianjiazai.dart';
TuPianJiaZai.jiazaiTupian(imageUrl: product.image,width: double.infinity,height: 200,fit: BoxFit.cover,
)

2. 完整参数说明

TuPianJiaZai.jiazaiTupian(// 必需参数imageUrl: String?, // 图片URL,可以为null// 可选参数width: double?, // 显示宽度height: double?, // 显示高度fit: BoxFit, // 图片填充方式,默认BoxFit.covercacheWidth: int?, // 缓存图片宽度,用于优化内存cacheHeight: int?, // 缓存图片高度,用于优化内存placeholder: Widget?, // 加载时显示的占位WidgeterrorWidget: Widget?, // 加载失败时显示的Widget
)

3. 使用案例

3.1 基础加载

TuPianJiaZai.jiazaiTupian(imageUrl: 'https://example.com/image.jpg',width: 200,height: 200,
)

3.2 自定义占位图和错误图

TuPianJiaZai.jiazaiTupian(imageUrl: imageUrl,width: 300,height: 200,placeholder: const Center(child: CircularProgressIndicator(),),
errorWidget: const Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Icon(Icons.error),Text('加载失败'),],),),
)

3.3 列表项中使用

ListView.builder(
itemBuilder: (context, index) {
return TuPianJiaZai.jiazaiTupian(
imageUrl: imageUrls[index],
height: 150,
fit: BoxFit.cover,
cacheWidth: 600, // 优化缓存大小
cacheHeight: 400,
);
},
)

请自行在\lib\utils\get_images\文件夹中创建一下配置

D:\F\luichun\lib\utils\get_images\huancunguanli.dart

import 'dart:io';
import 'dart:typed_data';
import 'package:path_provider/path_provider.dart';
import 'package:crypto/crypto.dart';
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:synchronized/synchronized.dart';
import 'logger.dart';  // 使用统一的日志管理器
import '../env_config.dart';// 本地进行开发时,使用 会对 localhost:10005 进行请求,但是安卓模拟器需要把localhost转换为 10.0.2.2/// 完整工作流程:
/// 1.应用启动 -> 初始化缓存目录
/// 2.请求图片 -> 检查缓存 -> 返回缓存或null
/// 3.下载图片 -> 保存图片 -> 更新映射关系
/// 4.定期维护 -> 清理缓存/计算大小/// 图片缓存管理器
/// 用于管理图片的本地缓存,减少重复的网络请求
class HuanCunGuanLi {/// 单例模式///   使用工厂构造函数确保全局只有一个缓存管理器实例///   避免重复创建缓存目录和资源浪费static final HuanCunGuanLi _instance = HuanCunGuanLi._internal();/// 缓存目录Directory? _cacheDir;/// 初始化锁final _lock = Lock();/// 初始化标志bool _isInitialized = false;/// 持久化存储的键名static const String _prefKey = 'image_cache_urls';// 工厂构造函数factory HuanCunGuanLi() {return _instance;}// 私有构造函数HuanCunGuanLi._internal();/// 确保已初始化Future<void> _ensureInitialized() async {if (_isInitialized) return;  // 快速检查await _lock.synchronized(() async {if (_isInitialized) return;  // 双重检查await init();});}/// 初始化缓存目录Future<void> init() async {try {final appDir = await getApplicationDocumentsDirectory();final cacheDir = Directory('${appDir.path}/image_cache');if (!await cacheDir.exists()) {await cacheDir.create(recursive: true);}_cacheDir = cacheDir;_isInitialized = true;if (EnvConfig.isDevelopment) {ImageLogger.logCacheInfo('缓存系统初始化完成: ${_cacheDir!.path}');}} catch (e) {ImageLogger.logCacheError('缓存系统初始化失败', error: e);rethrow;}}/// 3异步获取缓存图片/// 参数:///   url: 图片的网络地址/// 返回:///   Uint8List?: 图片的二进制数据,不存在时返回null/// 流程:///   1. 根据URL生成缓存键///   2. 查找本地缓存文件///   3. 返回缓存数据或nullFuture<Uint8List?> huoquTupian(String url) async {await _ensureInitialized();try {final cacheKey = _shengchengKey(url);final cacheFile = File('${_cacheDir!.path}/$cacheKey');if (await cacheFile.exists()) {ImageLogger.logCacheDebug('从缓存加载图片', {'url': url});return await cacheFile.readAsBytes();}return null;} catch (e) {ImageLogger.logCacheError('读取缓存图片失败', error: e);return null;}}/// 异步保存图片到缓存/// [url] 图片URL/// [imageBytes] 图片二进制数据Future<void> baocunTupian(String url, Uint8List imageBytes) async {await _ensureInitialized();final cacheKey = _shengchengKey(url);final cacheFile = File('${_cacheDir!.path}/$cacheKey');await cacheFile.writeAsBytes(imageBytes);await _baocunURLyingshe(url, cacheKey);}/// 生成缓存键/// 使用MD5加密URL生成唯一标识String _shengchengKey(String url) {final bytes = utf8.encode(url);final digest = md5.convert(bytes);return digest.toString();}/// 4. URL 映射管理:/// 保存URL映射关系/// 实现:///   1. 获取SharedPreferences实例///   2. 读取现有映射///   3. 更新映射关系///   4. 序列化并保存///   使用 SharedPreferences 持久化存储 URL 映射关系///   JSON 序列化保存映射数据///   异步操作避免阻塞主线程/// 保存URL映射关系Future<void> _baocunURLyingshe(String url, String cacheKey) async {final prefs = await SharedPreferences.getInstance();final Map<String, String> urlMap = await _huoquURLyingshe();urlMap[url] = cacheKey;await prefs.setString(_prefKey, jsonEncode(urlMap));}/// 获取URL映射关系Future<Map<String, String>> _huoquURLyingshe() async {final prefs = await SharedPreferences.getInstance();final String? mapJson = prefs.getString(_prefKey);if (mapJson != null) {return Map<String, String>.from(jsonDecode(mapJson));}return {};}/// 5.缓存清理功能:/// 清除所有缓存/// 使用场景:///   1. 应用清理存储空间///   2. 图片资源更新///   3. 缓存出现问题时重置/// 递归删除缓存目录/// 清除 URL 映射数据/// 清除所有缓存Future<void> qingchuHuancun() async {await _cacheDir!.delete(recursive: true);await _cacheDir!.create();final prefs = await SharedPreferences.getInstance();await prefs.remove(_prefKey);}///6 .缓存大小计算:///- 异步遍历缓存目录/// 累计所有文件大小/// 使用 Stream 处理大目录/// 获取缓存大小(字节)Future<int> huoquHuancunDaxiao() async {int size = 0;await for (final file in _cacheDir!.list()) {if (file is File) {size += await file.length();}}return size;}
}

D:\F\luichun\lib\utils\get_images\logger.dart

import 'package:logger/logger.dart';/// 图片加载系统的日志管理器
class ImageLogger {static final Logger _logger = Logger(printer: PrettyPrinter(methodCount: 0,errorMethodCount: 8,lineLength: 120,colors: true,printEmojis: true,dateTimeFormat: DateTimeFormat.onlyTimeAndSinceStart,),);// 缓存系统日志static void logCacheInfo(String message) {_logger.i('📦 $message');}static void logCacheError(String message, {dynamic error}) {_logger.e('📦 $message', error: error);}static void logCacheDebug(String message, [Map<String, dynamic>? data]) {if (data != null) {_logger.d('📦 $message\n${_formatData(data)}');} else {_logger.d('📦 $message');}}// 图片加载日志static void logImageInfo(String message) {_logger.i('🖼️ $message');}static void logImageError(String message, {dynamic error}) {_logger.e('🖼️ $message', error: error);}static void logImageDebug(String message, [Map<String, dynamic>? data]) {if (data != null) {_logger.d('🖼️ $message\n${_formatData(data)}');} else {_logger.d('🖼️ $message');}}static void logImageWarning(String message, [Map<String, dynamic>? data]) {if (data != null) {_logger.w('🖼️ $message\n${_formatData(data)}');} else {_logger.w('🖼️ $message');}}// 格式化数据为字符串static String _formatData(Map<String, dynamic> data) {return data.entries.map((e) => '  ${e.key}: ${e.value}').join('\n');}
}

D:\F\luichun\lib\utils\get_images\qinglihuancun.dart

// import 'package:flutter/material.dart';
import 'huancunguanli.dart';
import 'logger.dart';/// 缓存清理管理器
class QingLiHuanCun {static final HuanCunGuanLi _huancun = HuanCunGuanLi();/// 清理所有缓存static Future<void> qingliSuoyou() async {try {await _huancun.qingchuHuancun();ImageLogger.logCacheInfo('缓存清理完成');} catch (e) {ImageLogger.logCacheError('缓存清理失败', error: e);}}/// 获取当前缓存大小static Future<String> huoquDaxiao() async {try {final size = await _huancun.huoquHuancunDaxiao();// 转换为合适的单位if (size < 1024) return '$size B';if (size < 1024 * 1024) return '${(size / 1024).toStringAsFixed(2)} KB';return '${(size / (1024 * 1024)).toStringAsFixed(2)} MB';} catch (e) {ImageLogger.logCacheError('获取缓存大小失败', error: e);return '未知';}}/// 检查缓存大小并在超过阈值时清理static Future<void> jianchaHeQingli() async {try {final size = await _huancun.huoquHuancunDaxiao();// 如果缓存超过550MB,则清理if (size > 550 * 1024 * 1024) {await qingliSuoyou();}} catch (e) {ImageLogger.logCacheError('缓存检查失败', error: e);}}
}

D:\F\luichun\lib\utils\get_images\qinglihuancundeanniu.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';
import 'qinglihuancun.dart';/// 缓存配置
class CacheConfig {// 警告阈值  (当缓存超过500MB时显示警告)static const double warningThresholdMB = 500.0;// 自动清理阈值static const double autoCleanThresholdMB = 550.0;// 动画时长static const Duration animationDuration = Duration(milliseconds: 300);// 提示显示时长static const Duration snackBarDuration = Duration(seconds: 3);// 刷新动画时长static const Duration refreshAnimationDuration = Duration(milliseconds: 200);
}/// 清理完成回调
typedef OnCleanComplete = void Function(bool success);/// 缓存监听器
class CacheListener {static final List<VoidCallback> _listeners = [];static void addListener(VoidCallback listener) {_listeners.add(listener);}static void removeListener(VoidCallback listener) {_listeners.remove(listener);}static void notifyListeners() {for (var listener in _listeners) {listener();}}
}/// 自动清理调度器
class AutoCleanScheduler {static Timer? _timer;// 每24小时自动检查一次static void startSchedule() {_timer?.cancel();_timer = Timer.periodic(const Duration(hours: 24),(_) => QingLiHuanCun.jianchaHeQingli(),);}static void stopSchedule() {_timer?.cancel();_timer = null;}
}/// 缓存管理页面
class CacheManagementScreen extends StatelessWidget {const CacheManagementScreen({super.key});Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('缓存管理'),elevation: 0,),body: const SingleChildScrollView(child: QingLiHuanCunAnNiu(),),);}
}/// 缓存清理按钮组件
class QingLiHuanCunAnNiu extends StatefulWidget {final OnCleanComplete? onCleanComplete;const QingLiHuanCunAnNiu({super.key,this.onCleanComplete,});State<QingLiHuanCunAnNiu> createState() => _QingLiHuanCunAnNiuState();
}class _QingLiHuanCunAnNiuState extends State<QingLiHuanCunAnNiu> {String _cacheSize = '计算中...';bool _isClearing = false;Timer? _autoCheckTimer;DateTime? _lastClickTime;bool _isDoubleClick = false;void initState() {super.initState();_initializeCache();}void dispose() {_autoCheckTimer?.cancel();AutoCleanScheduler.stopSchedule();super.dispose();}// 初始化缓存Future<void> _initializeCache() async {await _huoquDaxiao();_startAutoCheck();AutoCleanScheduler.startSchedule();}// 启动自动检查// 每30分钟检查一次缓存大小void _startAutoCheck() {_autoCheckTimer?.cancel();_autoCheckTimer = Timer.periodic(const Duration(minutes: 30),(_) => _huoquDaxiao(),);}// 显示错误信息void _showError(String message) {if (!mounted) return;ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Row(children: [const Icon(Icons.error_outline, color: Colors.white),const SizedBox(width: 12),Expanded(child: Text(message)),],),backgroundColor: Colors.red,behavior: SnackBarBehavior.floating,duration: CacheConfig.snackBarDuration,shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8),),),);}// 显示警告信息void _showWarning() {if (!mounted) return;ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Row(children: [const Icon(Icons.warning_amber_rounded, color: Colors.white),const SizedBox(width: 12),const Expanded(child: Text('缓存较大,建议清理')),],),backgroundColor: Colors.orange,duration: CacheConfig.snackBarDuration,behavior: SnackBarBehavior.floating,shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8),),),);}// 获取缓存大小并检查Future<void> _huoquDaxiao() async {try {final size = await QingLiHuanCun.huoquDaxiao();if (!mounted) return;setState(() => _cacheSize = size);_checkCacheWarning(size);CacheListener.notifyListeners();} catch (e) {_showError('获取缓存大小失败: $e');}}// 检查缓存大小并显示警告void _checkCacheWarning(String size) {if (!size.contains('MB')) return;try {final double sizeInMB = double.parse(size.split(' ')[0]);if (sizeInMB > CacheConfig.warningThresholdMB) {_showWarning();}} catch (e) {// 解析错误处理}}// 显示清理进度void _showCleaningProgress() {if (!mounted) return;ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Row(children: [const SizedBox(width: 20,height: 20,child: CircularProgressIndicator(strokeWidth: 2,valueColor: AlwaysStoppedAnimation<Color>(Colors.white),),),const SizedBox(width: 16),const Text('正在清理缓存...'),],),duration: const Duration(seconds: 1),behavior: SnackBarBehavior.floating,shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8),),),);}// 检查是否是快速双击bool _checkDoubleClick() {final now = DateTime.now();if (_lastClickTime != null) {final difference = now.difference(_lastClickTime!);if (difference.inMilliseconds <= 1000) {  // 1秒内的双击_isDoubleClick = true;return true;}}_lastClickTime = now;_isDoubleClick = false;return false;}// 修改确认对话框逻辑Future<bool> _showConfirmDialog() async {if (_isClearing) return false;  // 防止重复清理// 检查是否是快速双击final isDoubleClick = _checkDoubleClick();// 如果不是双击,且缓存小于100MB,显示无需清理提示if (!isDoubleClick && _cacheSize.contains('MB')) {try {final double sizeInMB = double.parse(_cacheSize.split(' ')[0]);if (sizeInMB < 100.0) {// 显示缓存较小的提示if (mounted) {ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Row(children: [const Icon(Icons.info_outline, color: Colors.white),const SizedBox(width: 12),const Expanded(child: Text('缓存小于100MB,暂无需清理\n(快速双击可强制清理)'),),],),backgroundColor: Colors.blue,behavior: SnackBarBehavior.floating,duration: const Duration(seconds: 2),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8),),),);}return false;}} catch (e) {// 解析错误处理}}// 原有的确认对话框逻辑HapticFeedback.mediumImpact();final bool? confirm = await showDialog<bool>(context: context,builder: (context) => AlertDialog(title: Row(children: [const Icon(Icons.delete_outline, color: Colors.red),const SizedBox(width: 12),Text(_isDoubleClick ? '强制清理' : '确认清理'),],),content: Column(mainAxisSize: MainAxisSize.min,crossAxisAlignment: CrossAxisAlignment.start,children: [Text('当前缓存大小: $_cacheSize'),const SizedBox(height: 8),Text(_isDoubleClick ? '您选择了强制清理,确定要清理所有缓存吗?': '清理后将需要重新下载图片,确定要清理吗?'),],),actions: [TextButton(onPressed: () {HapticFeedback.lightImpact();Navigator.pop(context, false);},child: const Text('取消'),),TextButton(onPressed: () {HapticFeedback.lightImpact();Navigator.pop(context, true);},style: TextButton.styleFrom(foregroundColor: Colors.red,),child: Text(_isDoubleClick ? '强制清理' : '清理'),),],),);return confirm ?? false;}// 清理缓存Future<void> _qingliHuancun() async {final bool confirmed = await _showConfirmDialog();if (!confirmed) return;setState(() => _isClearing = true);try {_showCleaningProgress();await QingLiHuanCun.qingliSuoyou();if (mounted) {ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Row(children: [const Icon(Icons.check_circle_outline, color: Colors.white),const SizedBox(width: 12),const Text('缓存清理完成'),],),backgroundColor: Colors.green,behavior: SnackBarBehavior.floating,shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8),),),);await _huoquDaxiao();widget.onCleanComplete?.call(true);}} catch (e) {if (mounted) {_showError('清理失败: $e');widget.onCleanComplete?.call(false);}} finally {if (mounted) {setState(() => _isClearing = false);}}}Widget build(BuildContext context) {return Padding(padding: const EdgeInsets.all(16.0),child: Column(crossAxisAlignment: CrossAxisAlignment.stretch,children: [// 显示缓存大小Card(elevation: 0,shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12),side: BorderSide(color: Colors.grey.withOpacity(0.2),),),child: Padding(padding: const EdgeInsets.all(16.0),child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [const Text('缓存大小',style: TextStyle(fontSize: 16,fontWeight: FontWeight.bold,),),const SizedBox(height: 8),Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children: [Text(_cacheSize,style: const TextStyle(fontSize: 24,fontWeight: FontWeight.bold,),),IconButton(icon: AnimatedRotation(duration: CacheConfig.refreshAnimationDuration,turns: _isClearing ? 1 : 0,child: const Icon(Icons.refresh),),onPressed: _isClearing ? null : () async {try {setState(() => _isClearing = true);HapticFeedback.lightImpact();// 显示刷新提示ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Row(children: [SizedBox(width: 16,height: 16,child: CircularProgressIndicator(strokeWidth: 2,valueColor: AlwaysStoppedAnimation<Color>(Colors.white),),),SizedBox(width: 12),Text('正在刷新...'),],),duration: Duration(milliseconds: 200),behavior: SnackBarBehavior.floating,),);await _huoquDaxiao();} finally {if (mounted) {setState(() => _isClearing = false);}}},),],),],),),),const SizedBox(height: 16),// 清理按钮AnimatedContainer(duration: CacheConfig.animationDuration,transform: Matrix4.translationValues(0, _isClearing ? 4 : 0, 0,),child: ElevatedButton(onPressed: _isClearing ? null : () {HapticFeedback.mediumImpact();_qingliHuancun();},style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 16),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12),),),child: _isClearing? const SizedBox(width: 20,height: 20,child: CircularProgressIndicator(strokeWidth: 2),): const Text('清理缓存',style: TextStyle(fontSize: 16),),),),const SizedBox(height: 16),// 自动清理设置Card(elevation: 0,shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12),side: BorderSide(color: Colors.grey.withOpacity(0.2),),),child: ListTile(leading: const Icon(Icons.auto_delete),title: const Text('自动清理'),subtitle: Text('当缓存超过${CacheConfig.autoCleanThresholdMB}MB时自动清理'),trailing: const Icon(Icons.chevron_right),onTap: _isClearing ? null : () async {HapticFeedback.lightImpact();await QingLiHuanCun.jianchaHeQingli();await _huoquDaxiao();},),),],),);}
}

D:\F\luichun\lib\utils\get_images\tupianjiazai.dart

import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'dart:typed_data';
import 'huancunguanli.dart';
import '../env_config.dart';
import 'dart:isolate';
import 'logger.dart';/// 图片加载器类
/// 功能:处理异步图片加载、缓存和显示
/// 工作流程:
///   1. 接收图片URL请求
///   2. 检查本地缓存
///   3. 如无缓存,则在独立isolate中下载
///   4. 下载完成后保存到缓存
///   5. 返回图片数据用于显示
class TuPianJiaZai {static final HuanCunGuanLi _huancun = HuanCunGuanLi();static bool _initialized = false;/// 内部初始化方法/// 确保只初始化一次static Future<void> _ensureInitialized() async {if (!_initialized) {await _huancun.init();_initialized = true;ImageLogger.logCacheInfo('图片加载系统初始化完成');}}/// 网络请求客户端配置/// 功能:配置网络请求的基本参数/// 参数说明:///   - connectTimeout: 连接超时时间///   - receiveTimeout: 接收超时时间///   - headers: 请求头配置///   - followRedirects: 是否跟随重定向///   - maxRedirects: 最大重定向次数///   - validateStatus: 状态验证函数static final Dio _dio = Dio(BaseOptions(connectTimeout: const Duration(seconds: 30),receiveTimeout: const Duration(seconds: 60),sendTimeout: const Duration(seconds: 30),headers: {'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8','Accept-Encoding': 'gzip, deflate','Connection': 'keep-alive',},followRedirects: true,maxRedirects: 5,validateStatus: (status) => status != null && status < 500,responseType: ResponseType.bytes,receiveDataWhenStatusError: true,));/// 在独立isolate中加载图片/// 功能:创建新的isolate来处理图片下载,避免阻塞主线程/// 参数:///   url: 图片的网络地址/// 返回:///   Uint8List?: 图片的二进制数据,下载失败返回null/// 工作流程:///   1. 创建ReceivePort接收数据///   2. 启动新isolate处理下载///   3. 等待结果返回static Future<Uint8List?> _loadInIsolate(String url) async {final receivePort = ReceivePort();await Isolate.spawn(_isolateFunction, {'url': url,'sendPort': receivePort.sendPort,});final result = await receivePort.first;return result as Uint8List?;}/// Isolate工作函数/// 功能:在独立isolate中执行图片下载/// 参数:///   data: 包含url和sendPort的Map/// 工作流程:///   1. 解析传入参数///   2. 执行图片下载///   3. 通过sendPort返回结果static void _isolateFunction(Map<String, dynamic> data) async {final String url = data['url'];final SendPort sendPort = data['sendPort'];try {ImageLogger.logImageDebug('开始下载图片', {'url': url});int retryCount = 3;Response<List<int>>? response;while (retryCount > 0) {try {response = await _dio.get<List<int>>(EnvConfig.getImageUrl(url),options: Options(responseType: ResponseType.bytes,headers: {'Range': 'bytes=0-','Connection': 'keep-alive',},),onReceiveProgress: (received, total) {if (EnvConfig.isDevelopment) {ImageLogger.logImageDebug('下载进度', {'received': received, 'total': total});}},);break;} catch (e) {retryCount--;if (retryCount > 0) {ImageLogger.logImageWarning('图片下载失败,准备重试', {'url': url,'remainingRetries': retryCount,'error': e.toString()});await Future.delayed(Duration(seconds: 2));} else {rethrow;}}}if (response != null && (response.statusCode == 200 || response.statusCode == 206) && response.data != null) {final imageBytes = Uint8List.fromList(response.data!);sendPort.send(imageBytes);} else {ImageLogger.logImageWarning('图片下载失败', {'statusCode': response?.statusCode,'message': response?.statusMessage});sendPort.send(null);}} catch (e) {ImageLogger.logImageError('图片下载异常', error: e);sendPort.send(null);}}/// 加载网络图片的Widget/// 功能:提供图片加载的Widget封装/// 参数:///   imageUrl: 图片URL///   width: 显示宽度///   height: 显示高度///   fit: 图片填充方式///   placeholder: 加载占位Widget///   errorWidget: 错误显示Widget///   cacheWidth: 缓存宽度///   cacheHeight: 缓存高度/// 工作流程:///   1. 检查URL是否有效///   2. 使用FutureBuilder处理异步加载///   3. 根据不同状态显示不同Widgetstatic Widget jiazaiTupian({required String? imageUrl,double? width,double? height,BoxFit fit = BoxFit.cover,Widget? placeholder,Widget? errorWidget,int? cacheWidth,int? cacheHeight,}) {// 在实际使用时自动初始化_ensureInitialized();if (imageUrl == null) {return const SizedBox.shrink();}return FutureBuilder<Uint8List?>(future: _jiazaiTupianShuju(imageUrl),builder: (context, snapshot) {if (snapshot.connectionState == ConnectionState.waiting) {return placeholder ?? SizedBox(width: width,height: height,child: const Center(child: CircularProgressIndicator()),);}if (snapshot.hasError || snapshot.data == null) {if (EnvConfig.isDevelopment) {print('图片加载失败: ${snapshot.error}');print('URL: $imageUrl');}return errorWidget ?? SizedBox(width: width,height: height,child: const Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Icon(Icons.broken_image),Text('图片加载失败,请稍后重试'),],),),);}return Image.memory(snapshot.data!,key: ValueKey(imageUrl),width: width,height: height,fit: fit,cacheWidth: cacheWidth ?? (width?.toInt()),cacheHeight: cacheHeight ?? (height?.toInt()),gaplessPlayback: true,);},);}/// 加载图片数据/// 功能:处理图片加载的核心逻辑/// 参数:///   url: 图片URL/// 返回:///   Uint8List?: 图片二进制数据/// 工作流程:///   1. 检查本地缓存///   2. 如有缓存直接返回///   3. 无缓存则下载并保存static Future<Uint8List?> _jiazaiTupianShuju(String url) async {// 在实际使用时自动初始化await _ensureInitialized();final huancun = HuanCunGuanLi();// 先从缓存获取final cachedImage = await huancun.huoquTupian(url);if (cachedImage != null) {if (EnvConfig.isDevelopment) {print('从缓存加载图片: $url');}return cachedImage;}// 在独立isolate中加载图片final imageBytes = await _loadInIsolate(url);if (imageBytes != null) {// 保存到缓存await huancun.baocunTupian(url, imageBytes);}return imageBytes;}
} 

相关文章:

Flutter中的网络请求图片存储为缓存,与定制删除本地缓存

Flutter中的网络请求图片存储为缓存&#xff0c;与定制删除本地缓存 1&#xff1a;封装请求图片函数 2&#xff1a;访问的图片都会转为本地缓存&#xff0c;当相同的请求url&#xff0c;会在本地调用图片 3&#xff1a;本地缓存管理【windows与andriod已经测试】【有页面】【有…...

保障移动应用安全:多层次安全策略应对新兴威胁

在数字化时代&#xff0c;移动应用的安全问题变得越来越重要。随着网络威胁的不断升级&#xff0c;确保移动应用的安全性不仅是保护敏感数据的关键&#xff0c;也是维护用户信任的基础。为了应对复杂的安全挑战&#xff0c;企业必须采取先进的技术和多层次的安全策略&#xff0…...

【Linux】函数

一、函数 1、创建函数 如果定义了同名函数&#xff0c;则新定义的函数就会覆盖原先的定义的函数&#xff0c;而且在运行时不会报错。 创建函数的语法&#xff1a; 方法1&#xff1a;使用关键字function function name { commands } shell脚本中的函数名不能重复 方法2&#x…...

Maven中管理SNAPSHOT版本含义及作用

在开发过程中突然产生了一个疑问&#xff1a;IDEA中 maven deploy的依赖包的版本号,比如 1.0.0-SNAPSHOT是在哪配置的&#xff1f;在远程仓库中的版本和这个有关系吗 &#xff1f; 在 Maven 中&#xff0c;-SNAPSHOT 后缀是用于标识项目版本为快照&#xff08;Snapshot&#xf…...

win10 VS2019上libtorch库配置过程

win10 VS2019上libtorch库配置过程 0 引言1 获取libtorch2 在VS上配置使用libtorch库3 结语 0 引言 &#x1f4bb;&#x1f4bb;AI一下&#x1f4bb;&#x1f4bb;   libtorch库是一个用于深度学习的C库&#xff0c;是PyTorch的官方C前端。它提供了用于构建和训练深度学习模…...

【计算机网络】课程 实验二 交换机基本配置和VLAN 间路由实现

实验二 交换机基本配置和VLAN 间路由实现 一、实验目的 1&#xff0e;了解交换机的管理方式。 2&#xff0e;掌握通过Console接口对交换机进行配置的方法。 3&#xff0e;掌握交换机命令行各种模式的区别&#xff0c;能够使用各种帮助信息以及命令进行基本的配置。 4&…...

Oracle Dataguard(主库为单节点)配置详解(4):将主库复制到备库并启动同步

Oracle Dataguard&#xff08;主库为单节点&#xff09;配置详解&#xff08;4&#xff09;&#xff1a;将主库复制到备库并启动同步 目录 Oracle Dataguard&#xff08;主库为单节点&#xff09;配置详解&#xff08;4&#xff09;&#xff1a;将主库复制到备库并启动同步一、…...

OpenCL(贰):浅析CL内核程序接口函数

目录 1.前言 2.获取平台信息 1.cl_int类型 2.cl_platform_id类型 3.clGetPlatformIDs()&#xff1a;查询系统OpenCL平台数量或获取具体的平台信息 4.clGetPlatformInfo()&#xff1a;查询指定OpenCL平台的信息&#xff0c;例如平台名称、供应商、版本等 3.设置OpenCL上下文…...

Leetcode 3407. Substring Matching Pattern

Leetcode 3407. Substring Matching Pattern 1. 解题思路2. 代码实现 题目链接&#xff1a;3407. Substring Matching Pattern 1. 解题思路 这一题是一道leetcode easy的题目&#xff0c;照说应该没啥的&#xff0c;不过实际我做的时候在这里卡了一下&#xff0c;所以还是拿…...

学英语学压测:02jmeter组件-测试计划和线程组ramp-up参数的作用

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#xff1a;先看关键单词&#xff0c;再看英文&#xff0c;最后看中文总结&#xff0c;再回头看一遍英文原文&#xff0c;效果更佳&#xff01;&#xff01; 关键词 Functional Testing功能测试[ˈfʌŋkʃənəl ˈtɛstɪŋ]Sample样…...

Vue笔记-001-声明式渲染

https://cn.vuejs.org/tutorial/#step-2https://cn.vuejs.org/tutorial/#step-2 Vue 单文件组件 (Single-File Component&#xff0c;缩写为 SFC) 单文件组件是一种可复用的代码组织形式&#xff0c;它将从属于同一个组件的 HTML、CSS 和 JavaScript 封装在使用 .vue 后缀的文件…...

26考研资料分享 百度网盘

26考研资料分享考研资料合集 百度网盘&#xff08;仅供参考学习&#xff09; 基础班&#xff1a; 通过网盘分享的文件&#xff1a;2026【考研英语】等3个文件 链接: https://pan.baidu.com/s/1Q6rvKop3sWiL9zBHs87kAQ?pwd5qnn 提取码: 5qnn --来自百度网盘超级会员v3的分享…...

.NET 8 + Ocelot + Consul 实现代理网关、服务发现

.NET 8 Ocelot Consul 实现代理网关、服务发现 本文环境&#xff1a;.NET 8 Ocelot 23.4.2 Consul 1.7.14.6 1 实现网关 分别创建3个WebApi工程&#xff1a;OcelotGw、TestGwAService、TestGwBService&#xff1b;在OcelotGw工程中安装Ocelot包&#xff1a;Install-Packag…...

使用 Nginx 轻松处理跨域请求(CORS)

使用 Nginx 轻松处理跨域请求&#xff08;CORS&#xff09; 在现代 Web 开发中&#xff0c;跨域资源共享&#xff08;CORS&#xff09;是一种重要的机制&#xff0c;用于解决浏览器的同源策略限制。CORS 允许服务器声明哪些来源可以访问其资源&#xff0c;从而确保安全性与可用…...

【LeetCode Hot100 二分查找】搜索插入位置、搜索二维矩阵、搜索旋转排序数组、寻找两个正序数组的中位数

二分查找 搜索插入位置搜索二维矩阵在排序数组中查找元素的第一个和最后一个位置寻找旋转排序数组中的最小值搜索旋转排序数组寻找两个正序数组的中位数&#xff08;hard&#xff09; 搜索插入位置 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并…...

使用MediaPipe Face Mesh 面部动作检测

一、技术选型 OpenCV&#xff08;Open Source Computer Vision Library&#xff09; 用于视频流捕捉、图像预处理和基本图像处理操作。 MediaPipe 提供高效的人脸检测与关键点提取功能&#xff08;Face Mesh&#xff09;。 Python 作为后端开发语言&#xff0c;整合上述库进行…...

【Vue】<script setup>和 <script>区别是什么?在使用时的写法区别?

<script setup> 是 Vue 3 引入的一种新的脚本语法&#xff0c;它提供了一种更简洁和声明式的方式来编写组件逻辑。它是为了解决传统 <script> 标签在 Vue 单文件组件&#xff08;SFC&#xff09;中的一些局限性而设计的。 <script setup> 与 <script>…...

微服务框架,Http异步编程中,如何保证数据的最终一致性

一、背景 在微服务框架下&#xff0c;跨服务之间的调用&#xff0c;当遇到操作耗时或者量大的情况&#xff0c;我们一般会采用异步编程实现。 本文出现的问题是&#xff1a;异步回调过来时&#xff0c;却未查询到数据库中的任务&#xff0c;导致未能正常处理回调。 下面是当…...

vue3-dom-diff算法

vue3diff算法 什么是vue3diff算法 Vue3中的diff算法是一种用于比较虚拟DOM树之间差异的算法&#xff0c;其目的是为了高效地更新真实DOM&#xff0c;减少不必要的重渲染 主要过程 整个过程主要分为以下五步 前置预处理后置预处理仅处理新增仅处理后置处理包含新增、卸载、…...

年会抽奖Html

在这里插入图片描述 <!-- <video id"backgroundMusic" src"file:///D:/background.mp3" loop autoplay></video> --> <divstyle"width: 290px; height: 580px; margin-left: 20px; margin-top: 20px; background: url(D:/nianhu…...

功能安全实战系列09-英飞凌TC3xx LBIST开发详解

本文框架 0. 前言1.What?1.1 基本原理1.1.1 检测范围1.1.2 LBIST与锁步核对比1.1.3 控制寄存器1.2 关联Alarm2. How?2.1 LBIST触发?2.1.1 SSW配置自动触发2.1.2 软件手动触发LBIST2.2 实现策略2.3 测试篇LBIST对启动时间的影响如何确定当前LBIST是否已使能?如何确定当前LBI…...

Moldflow充填分析设置

1. 如何选择注塑机&#xff1a; 注塑机初选按注射量来选择&#xff1a; 点网格统计;选择三角形, 三角形体积就是产品的体积 47.7304 cm^3 点网格统计;选择柱体, 柱体的体积就是浇注系统的体积2.69 cm^3 所以总体积产品体积浇注系统体积 47.732.69 cm^3 材料的熔体密度与固体…...

vue3 + vite实现动态路由,并进行vuex持久化设计

在后台管理系统中&#xff0c;如何根据后端返回的接口&#xff0c;来动态的设计路由呢&#xff0c;今天一片文章带你们解 1、在vuex中设置一个方法 拿到完整的路由数据 const state {routerList: []}; const mutations { dynameicMenu(state, payload) {// 第一步 通过glob…...

科技创新驱动人工智能,计算中心建设加速产业腾飞​

在科技飞速发展的当下&#xff0c;人工智能正以前所未有的速度融入我们的生活。一辆辆无人驾驶的车辆在道路上自如地躲避车辆和行人&#xff0c;行驶平稳且操作熟练&#xff1b;刷脸支付让购物变得安全快捷&#xff0c;一秒即可通行。这些曾经只存在于想象中的场景&#xff0c;…...

学习设计模式《十二》——命令模式

一、基础概念 命令模式的本质是【封装请求】命令模式的关键是把请求封装成为命令对象&#xff0c;然后就可以对这个命令对象进行一系列的处理&#xff08;如&#xff1a;参数化配置、可撤销操作、宏命令、队列请求、日志请求等&#xff09;。 命令模式的定义&#xff1a;将一个…...

Chrome安装代理插件ZeroOmega(保姆级别)

目录 本文直接讲解一下怎么本地安装ZeroOmega一、下载文件在GitHub直接下ZeroOmega 的文件&#xff08;下最新版即可&#xff09; 二、安装插件打开 Chrome 浏览器&#xff0c;访问 chrome://extensions/ 页面&#xff08;扩展程序管理页面&#xff09;&#xff0c;并打开开发者…...

SON.stringify()和JSON.parse()之间的转换

1.JSON.stringify() 作用&#xff1a;将对象、数组转换成字符串 const obj {code: "500",message: "出错了", }; const jsonString JSON.stringify(obj); console.log(jsonString);//"{"code":"Mark Lee","message"…...

(33)课54--??:3 张表的 join-on 连接举例,多表查询总结。

&#xff08;112&#xff09;3 张表的 join-on 连接举例 &#xff1a; &#xff08;113&#xff09; 多表查询总结 &#xff1a; &#xff08;114&#xff09;事务 &#xff1a; &#xff08;115&#xff09; &#xff08;116&#xff09; &#xff08;117&#xff09; …...

网页前端开发(基础进阶4--axios)

Ajax Ajax(异步的JavaScript和XML) 。 XML是可扩展标记语言&#xff0c;本质上是一种数据格式&#xff0c;可以用来存储复杂的数据结构。 可以通过Ajax给服务器发送请求&#xff0c;并获取服务器响应的数据。 Ajax采用异步交互&#xff1a;可以在不重新加载整个页面的情况下&am…...

《100天精通Python——基础篇 2025 第5天:巩固核心知识,选择题实战演练基础语法》

目录 一、踏上Python之旅二、Python输入与输出三、变量与基本数据类型四、运算符五、流程控制 一、踏上Python之旅 1.想要输出 I Love Python,应该使用()函数。 A.printf() B.print() C.println() D.Print() 在Python中想要在屏幕中输出内容&#xff0c;应该使用print()函数…...