当前位置: 首页 > 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已经测试】【有页面】【有…...

智汇厦门:苏哒智能携其智能化产品亮相文心中国行现场

2025年1月2日&#xff0c;文心中国行再次踏足美丽的鹭岛厦门。 本次的文心中国行活动不仅有来自政府、高校及企业的精英专家将齐聚一堂&#xff0c;分享AI与大模型的最新研究成果&#xff0c;还正式揭牌百度飞桨&#xff08;厦门&#xff09;人工智能产业赋能中心&#xff0c;…...

SQL 分析函数与聚合函数的组合应用

目标&#xff1a;掌握 SQL 中分析函数&#xff08;窗口函数&#xff09;与聚合函数的组合使用&#xff0c;通过实际案例实现复杂业务需求&#xff0c;如同比、环比和趋势分析。 1. 分析函数与聚合函数的区别 聚合函数&#xff08;Aggregate Functions&#xff09;&#xff1a;…...

【Elasticsearch入门到落地】5、安装IK分词器

接上篇《4、Elasticsearch的安装》 上一篇我们进行了Elasticsearch以及Kibana的环境准备及软件安装&#xff0c;本篇我们安装最后一个支持软件IK分词器。 一、IK分词器概念 我们再来回顾一下上一张IK分词器的概念&#xff1a; IK分词器&#xff08;IK Analyzer&#xff09;是…...

8、RAG论文笔记(Retrieval-Augmented Generation检索增强生成)

RAG论文笔记 1、 **研究背景与动机**2、方法概述3、RAG 模型架构3.1总体架构3.2 Generator&#xff08;生成器&#xff09;3.3 检索器&#xff08;Retriever&#xff09;3.4训练&#xff08;Training&#xff09;3.5**解码方法**&#xff08;求近似 &#xff09;3.6微调的参数 …...

【论文笔记】Contrastive Learning for Sign Language Recognition and Translation

&#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平。 基本信息 标题: Contrastive Learning for…...

《C++设计模式》策略模式

文章目录 1、引言1.1 什么是策略模式1.2 策略模式的应用场景1.3 本文结构概览 2、策略模式的基本概念2.1 定义与结构2.2 核心角色解析2.2.1 策略接口&#xff08;Strategy&#xff09;2.2.2 具体策略实现&#xff08;ConcreteStrategy&#xff09;2.2.3 上下文&#xff08;Cont…...

细说STM32F407单片机轮询方式CAN通信

目录 一、项目介绍 二、项目配置 1、时钟、DEBUG、USART6、NVIC、GPIO、CodeGenerator 2、CAN1 &#xff08;1&#xff09;Bit Timings Parameters组&#xff0c;位时序参数 &#xff08;2&#xff09;Basic Parameters组&#xff0c;基本参数 &#xff08;3&#xff09…...

perf:对hutool的BeanUtil工具类做补充

分享一个自定义的BeanUtil&#xff0c;继承的是hutool的工具类&#xff0c;然后自己扩充了几个方法&#xff1b; 1、实现了两个对象覆盖非空属性的功能&#xff08;经常使用&#xff09;&#xff0c;不需要设置CopyOptions&#xff1b; 2、两个对象&#xff0c;对指定前缀的属…...

【数据结构】栈与队列(FIFO)

在阅读该篇文章之前&#xff0c;可以先了解一下堆栈寄存器和栈帧的运作原理&#xff1a;<【操作系统】堆栈寄存器sp详解以及栈帧>。 栈(FILO) 特性: 栈区的存储遵循着先进后出的原则。 例子: 枪的弹夹&#xff0c;最先装进去的子弹最后射出来&#xff0c;最后装入的子弹…...

02.01、移除重复节点

02.01、[简单] 移除重复节点 1、题目描述 编写代码&#xff0c;移除未排序链表中的重复节点。保留最开始出现的节点。 2、解题思路 为了实现这一目标&#xff0c;我们可以使用一个哈希表&#xff08;或集合&#xff09;来记录已经遇到的节点值&#xff0c;逐步遍历链表并删…...

Spring thymeleaf 的快速默认搭建使用

Spring thymeleaf 的快速默认搭建使用 thymeleaf 的搭建Pom 文件 thymeleaf 的使用Controller返回参数String资源文件路径访问端点显示HTML页面 thymeleaf 的搭建 Pom 文件 Pom 文件引入 spring-boot-starter-thymeleaf 依赖 <dependency><groupId>org.springfra…...

unity学习3:如何从github下载开源的unity项目

目录 1 网上别人提供的一些github的unity项目 2 如何下载github上的开源项目呢&#xff1f; 2.1.0 下载工具 2.1.1 下载方法1 2.1.2 下载方法2&#xff08;适合内部项目&#xff09; 2.1.3 第1个项目 和第4项目 的比较 第1个项目 第2个项目 第3个项目 2.1.4 下载方法…...

印象笔记07——试一试PDF标注

印象笔记07——试一试PDF标注 [!CAUTION] 根据第六期&#xff0c;我再次查询了资料&#xff0c;印象笔记还是有一些可圈可点的功能的&#xff08;当然部分有平替&#xff09;&#xff0c;针对会员作用&#xff0c;开发使用场景虽然是逆向的&#xff0c;但我坚信这是一部分人的现…...

Logback的使用

1、基本认识 logback官方文档&#xff1a;http://logback.qos.ch 具体样例&#xff1a;https://www.baeldung.com/logback 从下面依赖关系图可以看见&#xff0c;Springboot的核心启动器spring-boot-stater依赖了spring-boot-starter-looging&#xff0c;而这个就是日志的启动器…...

沙箱模拟支付宝支付3--支付的实现

1 支付流程实现 演示案例 主要参考程序员青戈的视频【支付宝沙箱支付快速集成版】支付宝沙箱支付快速集成版_哔哩哔哩_bilibili 对应的源码在 alipay-demo: 使用支付宝沙箱实现支付功能 - Gitee.com 以下是完整的实现步骤 1.首先导入相关的依赖 <?xml version"1…...

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

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

Redis的常用命令

Redis中文字典网站 redis 命令手册https://redis.com.cn/commands.html Keys * 查看当前库所有的key exists ke 判断某个key是否存在 type key查看你的key是什么类型 Del key删除执行的key数据 unlink key非阻塞删除&#xff0c;仅仅将keys从keyspace元数据中删除&#xf…...

国内Ubuntu环境Docker部署 ComfyUI

国内Ubuntu环境Docker部署 ComfyUI 趁着这两天用docker部署了 Stable Diffusion&#xff0c;顺手也安排上 ComfyUI。 ComfyUI相比 Stable Diffusion 原生的 WEB UI&#xff0c;更容易让人了解其出图的过程&#xff0c;极其适合学习与研究。拼接其强大的插件节点、不仅能够实现文…...

Meta 的新策略,将 AI 生成的角色整合到其社交媒体平台

一、Meta新年规划及引人注目的举措 多元规划背景&#xff1a;在新的一年&#xff0c;Meta制定了多维度的战略规划&#xff0c;旨在巩固并拓展其在科技领域的影响力。增强现实与元宇宙是其长期布局的重点方向&#xff0c;期望借此塑造未来互联网的交互形态&#xff1b;面对TikTo…...

玩转OCR | 腾讯云智能结构化OCR初次体验

目录 一、什么是OCR&#xff08;需要了解&#xff09; 二、产品概述与核心优势 产品概述 智能结构化能做什么 举例说明&#xff08;选看&#xff09; 1、物流单据识别 2、常见证件识别 3、票据单据识别 4、行业材料识别 三、产品特性 高精度 泛化性 易用性 四、…...

蓝桥杯JAVA--003

需求 2.代码 public class RegularExpressionMatching {public boolean isMatch(String s, String p) {if (p.isEmpty()) {return s.isEmpty();}boolean firstMatch !s.isEmpty() && (s.charAt(0) p.charAt(0) || p.charAt(0) .);if (p.length() > 2 && p…...

STC51和STM32单片机烧录引脚的完整名称

STC51 和 STM32 单片机烧录引脚的完整名称 1. STC51 单片机的烧录引脚 STC51 单片机通过 串口&#xff08;UART&#xff09; 进行程序下载&#xff0c;主要引脚如下&#xff1a; 引脚名称完整英文名称说明TXDTransmit Data串口发送引脚&#xff0c;用于发送数据。RXDReceive…...

阿里云大模型ACP高级工程师认证模拟试题

阿里云大模型ACP高级工程师认证模拟试题 0. 引言1. 模拟试题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题单选题单选题多选题多选题单选题多选题单…...

深入理解计算机中的补码、反码、原码

问题&#xff1a; 我们每天用的钟表&#xff0c;其实只有1~12这12个数字&#xff0c;但我们日常会说13点、17点之类的。 问&#xff1a;13点在钟表上哪个位置&#xff1f; 答&#xff1a;很简单嘛&#xff0c;1点的位置。 你不觉得奇怪吗&#xff0c;为啥13点会和1点在同一个位…...

调试:用电脑开发移动端网页,然后用手机真机调试

一、背景 电脑开发移动端&#xff0c;然后想真机调试... 二、实现 2.1、电脑和手机链接相同局域网 2.2、pnpm run dev 启动项目 2.3、浏览器访问 localhost:3001/login 2.4、Windowsr 输入cmd&#xff0c;在cmd输入 ipconfig 2.5、浏览器访问 ip地址加/login 2.6、手机端…...

深入浅出:Spring Boot 自定义消息转换器的实现与应用

Spring Boot 作为当前最流行的 Java Web 开发框架之一&#xff0c;广泛应用于微服务架构、企业级应用等多个场景。Spring Boot 提供了灵活且易于扩展的架构&#xff0c;其中消息转换器&#xff08;Message Converter&#xff09;是其重要组成部分。消息转换器在 Spring Boot 中…...

基于AI大模型的医院SOP优化:架构、实践与展望

一、引言 1.1 研究背景与意义 近年来,人工智能(AI)技术取得了迅猛发展,尤其是大模型的出现,为各个领域带来了革命性的变化。在医疗领域,AI 医疗大模型正逐渐崭露头角,展现出巨大的应用潜力。随着医疗数据的海量积累以及计算能力的大幅提升,AI 医疗大模型能够对复杂的…...

Maven项目集成SQL Server的完整教程:从驱动配置到封装优化

前言 在最近的系统对接过程中&#xff0c;由于对方团队不熟悉技术&#xff0c;最终选择直接提供 SQL Server 视图。本文详细记录了使用 Maven 集成 SQL Server 驱动的过程&#xff0c;以及从配置到查询的各个关键步骤&#xff0c;还包括注意事项与常见问题&#xff0c;希望对需…...

Java 21 优雅和安全地处理 null

在 Java 21 中,判断 null 依然是开发中常见的需求。通过使用现代 Java 提供的工具和特性,可以更加优雅和安全地处理 null。 1. 使用 Objects.requireNonNull Objects.requireNonNull 是标准的工具方法,用于快速判断并抛出异常。 示例 import java.util.Objects;public c…...

Java(四十四)file

Java中的file类:代表文件或者文件夹(目录)类,也就是说将文件或者文件夹通过File类来封装成对象。 一:常用的构造方法: 使用file类,需要通过构造方法创建一个file对象。 1:public File(String pathname) public static void main(String[] args) {File fl = new File(&…...

【51项目】51单片机自制小霸王游戏机

视频演示效果: 纳新作品——小霸王游戏机 目录: 目录 视频演示效果: 目录: 前言:...

【ArcGISPro/GeoScenePro】检查多光谱影像的属性并优化其外观

数据 https://arcgis.com/sharing/rest/content/items/535efce0e3a04c8790ed7cc7ea96d02d/data 操作 其他数据 检查影像的属性 熟悉检查您正在使用的栅格属性非常重要。...

《新概念模拟电路》-三极管

三极管 本系列文章主要学习《新概念模拟电路》中的知识点。在工作过程中&#xff0c;碰到一些问题&#xff0c;于是又翻阅了模电这本书。我翻阅的是ADI出版的&#xff0c;西安交通大学电工中心杨建国老师编写的模电书。 <模电>和《数电》这两本书是电子学的专业基础课&…...

K 近邻算法入门指南:明氏距离与皮尔森距离的基础讲解

1、K近邻算法介绍 K近邻(k-Nearest Neighbor&#xff0c;KNN)分类算法的思路是&#xff1a;在特征空间中&#xff0c;如果一个样本附近的k个最近样本的大多数属于某一个类别&#xff0c;则该样本也属于这个类别。K近邻算法中&#xff0c;所选择的邻居都是已经正确分类的对象。…...

如何验证imap是否生效

要验证您的 Outlook 邮箱是否启用了 IMAP 并且正常工作&#xff0c;可以按照以下步骤进行操作&#xff1a; 1. 确认 Outlook 邮箱是否启用 IMAP 步骤&#xff1a; 登录到您的 Outlook Web 账户&#xff1a; 打开浏览器&#xff0c;访问 Outlook.com 或 Microsoft 365 Outlook…...

MySQL 06 章——多表查询

多表查询&#xff0c;也称为关联查询&#xff0c;是指两个表或多个表一起完成查询操作 前提条件&#xff0c;这些一起查询的表之间是有关系的&#xff08;一对一、一对多&#xff09;&#xff0c;它们之间一定是有关联字段的。这个关联字段可能建立了外键&#xff0c;也可能没…...

转换VMware Esxi 虚拟机到 Windows2019 Hyper-V Server

Hyper-v专用P2V工具disk2vhd实际应用 工具介绍 disk2vhd是一个非常小的P2V转换工具&#xff0c;可以将你的物理服务器或Esxi vm 转换成为VHD或者vhdx格式的虚拟硬盘文件&#xff0c;然后在虚拟平台上作为一台虚拟机来使用。目前disk2vhd的最新版本是2.0.1&#xff0c;已经可以…...

头歌实训2-1:面向对象程序设计-基础部分

第1关&#xff1a;定义银行员工类BankEmployee 本关任务&#xff1a;编写银行员工类BankEmployee&#xff0c;要求&#xff1a; 1.银行员工类的属性包括姓名name&#xff0c;工号num&#xff0c;工资salary 2.姓名name和工号num设置为私有属性,并将salay设置为默认参数3000 平…...

超高分辨率 图像 分割处理

文章大纲 制造业半导体领域高分辨率图像半导体数据集开源的高分辨率晶圆图像数据集1. WM-811K数据集2. Kaggle上的WM-811K Clean Subset数据集医疗 病理领域高分辨率图像1. Camelyon+2. CAMELYON173. CPIA Dataset4. UCF-WSI-Dataset航拍 遥感中的高分辨率 图像航拍遥感领域高分…...

使用 apply 方法将其他列的值传入 DataFrame 或 Series 的函数,来进行更灵活的计算或操作

可以使用 apply 方法将其他列的值传入 DataFrame 或 Series 的函数&#xff0c;来进行更灵活的计算或操作。apply 方法允许你逐行或逐列地对 DataFrame 或 Series 的元素进行操作&#xff0c;而且你可以将其他列的值作为参数传递给函数。 示例&#xff1a;使用 apply 结合其他…...

[CTF/网络安全] 攻防世界 warmup 解题详析

查看页面源代码&#xff0c;发现source.php 得到一串代码&#xff0c;进行代码审计&#xff1a; <?phpclass emmm{public static function checkFile(&$page){$whitelist ["source">"source.php","hint">"hint.php"];…...

力扣第389题—找不同

class Solution:def findTheDifference(self, s: str, t: str) -> str:# 对字符串 s 和 t 进行排序a sorted(s)b sorted(t)# 比较排序后的两个列表for i in range(len(a)):if a[i] ! b[i]:return b[i]# 如果前面的比较没有找到差异&#xff0c;那么差异字符在 t 的最后一个…...

vite6+vue3+ts+prettier+eslint9配置前端项目(后台管理系统、移动端H5项目通用配置)

很多小伙伴苦于无法搭建一个规范的前端项目&#xff0c;导致后续开发不规范&#xff0c;今天给大家带来一个基于Vite6TypeScriptVue3ESlint9Prettier的搭建教程。 目录 一、基础配置1、初始化项目2、代码质量风格的统一2.1、配置prettier2.2、配置eslint2.3、配置typescript 3、…...

滴滴数据分析80道面试题及参考答案

如何衡量分类好坏? 衡量分类好坏有多种方法,常用的有准确率、精确率、召回率、F1 值、ROC 曲线与 AUC 值等。 准确率:是指分类正确的样本数占总样本数的比例,计算公式为:准确率 = (分类正确的样本数)/(总样本数)。准确率越高,说明分类器整体的分类效果越好,但在正负…...

嵌入式应用软件开发中C语言方向面试题

嵌入式应用软件开发中C语言方向面试题随笔 前言一、C语言基础二、嵌入式开发相关三、硬件相关知识五、实际编程问题前言 做嵌入式开发这么多年了,简单记录下C语言方向常见面试题,这里是应用软件方向的。 一、C语言基础 C语言的指针与数组的区别是什么?指针:指针是一个变量…...

vue3中mixins替代方案

使用自定义 Hooks&#xff08;Composables&#xff09; 自定义 Hooks 是一种基于函数的代码复用方式&#xff0c;可以在 setup 函数中使用。它允许将组件的逻辑分割成更小的、可复用的部分。 useCounter.js //useCounter.js import { ref, onMounted } from vue;export func…...

线性代数自学资源推荐我的个人学习心得

1.前言 自己这个学期的课程基本上就结束了&#xff0c;因此我自己就开始学习下个学期的课程--线性代数&#xff0c;也是我们在大学里面的最后一门数学课程了&#xff1b; 之前有过一些这个线性代数的基础&#xff0c;当时主要是参加这个数学建模比赛去学习这个matlab吗&#…...

WordPress Crypto 插件 身份认证绕过漏洞复现(CVE-2024-9989)

0x01 产品简介 WordPress Crypto插件是指那些能够为WordPress网站提供加密货币支付、信息显示或交易功能的插件。这些插件通常与WordPress电子商务插件(如WooCommerce)集成,使网站能够接受多种加密货币支付,或展示加密货币实时信息。支持多种加密货币支付,付款直接进入钱…...

软件逆向之OD基础

OD程序目录 plugin&#xff1a;存放OD所有插件 UDD&#xff1a;存放程序临时的数据&#xff0c;比如&#xff1a;程序注释、断点等 ollydbg.ini&#xff1a;存放OD自身配置的属性表 OLLYDBG.HLP&#xff1a;OD的帮助手册 OD断点 1.软件断点&#xff1a; 介绍&#xff1a…...