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;}
} 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/501528.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无线AP安装注意事项

现在的办公楼、酒店等项目中都设计含有网络无线覆盖这一项&#xff0c;在项目实施中&#xff0c;往往采用的是便捷并且后期便于网络无线设备管理的无线ap设备&#xff0c;作为前端无线信号的覆盖。在具体安装无线AP过程中&#xff0c;我们必须要注意以下几点才能保证项目实施完…

Golang的容器编排实践

Golang的容器编排实践 一、Golang中的容器编排概述 作为一种高效的编程语言&#xff0c;其在容器编排领域也有着广泛的运用。容器编排是指利用自动化工具对容器化的应用进行部署、管理和扩展的过程&#xff0c;典型的容器编排工具包括Docker Swarm、Kubernetes等。在Golang中&a…

计算机毕业设计Django+Tensorflow音乐推荐系统 音乐可视化 卷积神经网络CNN LSTM音乐情感分析 机器学习 深度学习 Flask

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

C# 在PDF中添加和删除水印注释 (Watermark Annotation)

目录 使用工具 C# 在PDF文档中添加水印注释 C# 在PDF文档中删除水印注释 PDF中的水印注释是一种独特的注释类型&#xff0c;它通常以透明的文本或图片形式叠加在页面内容之上&#xff0c;为文档添加标识或信息提示。与传统的静态水印不同&#xff0c;水印注释并不会永久嵌入…

分析服务器 systemctl 启动gozero项目报错的解决方案

### 分析 systemctl start beisen.service 报错 在 Linux 系统中&#xff0c;systemctl 是管理系统和服务的主要工具。当我们尝试重启某个服务时&#xff0c;如果服务启动失败&#xff0c;systemctl 会输出错误信息&#xff0c;帮助我们诊断和解决问题。 本文将通过一个实际的…

Dubbo扩展点加载机制

加载机制中已经存在的一些关键注解&#xff0c;如SPI、©Adaptive> ©Activateo然后介绍整个加载机制中最核心的ExtensionLoader的工作流程及实现原理。最后介绍扩展中使用的类动态编译的实 现原理。 Java SPI Java 5 中的服务提供商https://docs.oracle.com/jav…

如何利用Logo设计免费生成器创建专业级Logo

在当今的商业世界中&#xff0c;一个好的Logo是品牌身份的象征&#xff0c;它承载着公司的形象与理念。设计一个专业级的Logo不再需要花费大量的金钱和时间&#xff0c;尤其是当我们拥有Logo设计免费生成器这样的工具时。接下来&#xff0c;让我们深入探讨如何利用这些工具来创…

游戏如何检测iOS越狱

不同于安卓的开源生态&#xff0c;iOS一直秉承着安全性更高的闭源生态&#xff0c;系统中的硬件、软件和服务会经过严格审核和测试&#xff0c;来保障安全性与稳定性。 据FairGurd观察&#xff0c;虽然iOS系统具备一定的安全性&#xff0c;但并非没有漏洞&#xff0c;如市面上…

智联视频超融合平台:电力行业的智能守护者

文章目录 一、远程实时监控与设备状态监测二、提高应急响应能力三、实现无人值守与减员增效四、保障电力设施安全与防范外部破坏五、提升电网运行管理效率与决策科学性六、助力电力企业数字化转型与智能化发展七、智联视频超融合平台 在当今数字化浪潮下&#xff0c;视频联网平…

卸载干净 IDEA(图文讲解)

目录 1、卸载 IDEA 程序 2、注册表清理 3、残留清理 1、卸载 IDEA 程序 点击屏幕左下角 Windows 图标 -> 设置-控制面板->intellij idea 勾选第一栏 Delete IntelliJ IDEA 2022.2 caches and local history&#xff0c;表示同时删除 IDEA 本地缓存以及历史。 Delete I…

计算机网络•自顶向下方法:路由选路算法

路由选路算法 在网络层中&#xff0c;选路是指数据包从源主机到目的主机的传输过程中&#xff0c;如何通过网络中的路由器选择一条合适的路径。路由器根据网络拓扑、路由表、协议规则等来决定如何将数据包转发到下一跳&#xff0c;直到数据包到达目的地。 选路算法分类 静态算…

Qemu配置QXL显卡支持分辨率

默认情况下&#xff0c;创建的vm的视频RAM限制为16MB。在win操作系统中分辨率最高就只能调到1024x768。 <video><model typecirrus vram16384 heads1 primaryyes/><address typepci domain0x0000 bus0x00 slot0x02 function0x0/> </video>单单修改ram…

【区块链】零知识证明基础概念详解

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 零知识证明基础概念详解引言1. 零知识证明的定义与特性1.1 基本定义1.2 三个核心…

Redis面试相关

Redis开篇 使用场景 缓存 缓存穿透 解决方法一&#xff1a; 方法二&#xff1a; 通过多次hash来获取对应的值。 小结 缓存击穿 缓存雪崩 打油诗 双写一致性 两种不同的要求 强一致 读锁代码 写锁代码 强一致&#xff0c;性能低。 延迟一致 方案一&#xff1a;消息队列 方…

以太网协议和LWIP协议详解

一、以太网协议简介 以太网是一种产生较早&#xff0c;使用相当广泛的局域网技术。目前以太网根据速度等级分类大概分为&#xff1a;标准以太网&#xff08;10Mbit/s&#xff09;&#xff0c;快速以太网&#xff08;100Mbit/s&#xff09;&#xff0c;千兆以太网&#xff08;1…

Qt|QWidget窗口支持旋转

功能实现&#xff1a;使用QWidget创建的窗口支持窗口旋转功能。 展示的示例中支持由水平方向旋转至垂直方向。至于其它角度旋转的问题&#xff0c;看完这篇文章后应该会很简单能实现的&#xff01; 开发环境&#xff1a;win VS2019 Qt 5.15.2 在实现之前也有想用使用 QProp…

微信小程序滑动解锁、滑动验证

微信小程序简单滑动解锁 效果 通过 movable-view &#xff08;可移动的视图容器&#xff0c;在页面中可以拖拽滑动&#xff09;实现的简单微信小程序滑动验证 movable-view 官方说明&#xff1a;https://developers.weixin.qq.com/miniprogram/dev/component/movable-view.ht…

微服务实战——购物车模块实战

购物车 1. 数据模型分析 1.1. 需求描述 用户可以在登录状态下将商品添加到购物车【用户购物车/在线购物车】 放入数据库mongodb放入 redis&#xff08;采用&#xff09; 登录以后&#xff0c;会将临时购物车的数据全部合并过来&#xff0c;并清空临时购物车&#xff1b; 用…

1961-2022年中国大陆多干旱指数数据集(SPI/SPEI/EDDI/PDSI/SC-PDSI/VPD)

DOI: 10.5194/essd-2024-270 干旱指数对于评估和管理缺水和农业风险至关重要;然而&#xff0c;现有数据集中缺乏统一的数据基础&#xff0c;导致不一致&#xff0c;对干旱指数的可比性提出了挑战。本研究致力于创建CHM_Drought&#xff0c;这是一个创新且全面的长期气象干旱数…

xilinx的高速接口构成原理和连接结构及ibert工具的使用-以k7 GTX为例

一、相关简介 Xilinx的高速接口称之为transceivers(高速收发器&#xff09;&#xff0c;这部分的电路是专用电路&#xff0c;供电等都是独立的&#xff0c;根据速率可以分为GTP/GTX/GTH/GTY/GTM等。 Xilinx的高速接口是QUAD为单位的&#xff0c;没一个QUAD由一个时钟COMMON资…