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

news2025/1/8 4:30:11

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.cover
                        cacheWidth: int?, // 缓存图片宽度,用于优化内存
                        cacheHeight: int?, // 缓存图片高度,用于优化内存
                        placeholder: Widget?, // 加载时显示的占位Widget
                        errorWidget: 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. 返回缓存数据或null
  Future<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. 根据不同状态显示不同Widget
  static 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.coloradmin.cn/o/2272999.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android设备使用AOA协议进行主机与配件模式通信

1.使用TYPC-C数据线连接两台华为手机&#xff1a; TYPE-C线&#xff0c;先连接下图右边的ACCESSORY 再连接左边的HOST 此时左边的HOST(白色) 会给右边的ACCESSORY(黑色) 充电 接着打开左连接的HostChart会自动调起授权&#xff0c;然后会启动右边的AccessoryChart USB HOS…

机器学习基础-支持向量机SVM

目录 基本概念和定义 1. 超平面&#xff08;Hyperplane&#xff09; 2. 支持向量&#xff08;Support Vectors&#xff09; 3. 线性可分 4. 边界 SVM算法基本思想和分类 基本思想 间隔最大化 间隔&#xff08;Margin&#xff09; 软边距 SVM 核函数的概念 基本概念…

ubuntu开机启动服务

需求背景&#xff1a; 需要监控日志&#xff0c;每次都是手动启动 nohup ./prometheus >/dev/null & nohub ./node_exporter >/dev/null & 需求目标&#xff1a; 重启后系统自动启动服务

图漾相机基础操作

1.客户端概述 1.1 简介 PercipioViewer是图漾基于Percipio Camport SDK开发的一款看图软件&#xff0c;可实时预览相机输出的深度图、彩色图、IR红外图和点云图,并保存对应数据&#xff0c;还支持查看设备基础信息&#xff0c;在线修改gain、曝光等各种调节相机成像的参数功能…

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

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

【论文笔记】QLoRA: Efficient Finetuning of Quantized LLMs

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

Apache Paimon-实时数据湖

一、Apache Paimon是什么? Flink社区希望能够将 Flink 的 Streaming 实时计算能力和 Lakehouse 新架构优势进一步结合&#xff0c;推出新一代的 Streaming Lakehouse 技术&#xff0c;促进数据在数据湖上真正实时流动起来&#xff0c;并为用户提供实时离线一体化的开发体验。 …

为什么相关性不是因果关系?人工智能中的因果推理探秘

目录 一、背景 &#xff08;一&#xff09;聚焦当下人工智能 &#xff08;二&#xff09;基于关联框架的人工智能 &#xff08;三&#xff09;基于因果框架的人工智能 二、因果推理的基本理论 &#xff08;一&#xff09;因果推理基本范式&#xff1a;因果模型&#xff0…

AI Development Notes 1 - introduction with the OpenAI API Development

Official document&#xff1a;https://platform.openai.com/docs/api-reference/chat/create 1. Use APIfox to call APIs 2.Use PyCharm to call APIs 2.1-1 WIN OS.Configure the Enviorment variable #HK代理环境&#xff0c;不需要科学上网(价格便宜、有安全风险&#…

路由组件与一般组件的区别

路由组件与一般组件的区别 1. 基本概念 1.1 路由组件 路由组件是指通过路由规则映射的组件&#xff0c;通常放在 pages 或 views 文件夹中。 1.2 一般组件 一般组件是指通过 import 导入后直接使用的组件&#xff0c;通常放在 components 文件夹中。 2. 主要区别 2.1 存…

K8s高可用集群之Kubernetes集群管理平台、命令补全工具、资源监控工具部署及常用命令

K8s高可用集群之Kubernetes管理平台、补全命令工具、资源监控工具部署及常用命令 1.Kuboard可视化管理平台2.kubectl命令tab补全工具3.MetricsServer资源监控工具4.Kubernetes常用命令 1.Kuboard可视化管理平台 可以选择安装k8s官网的管理平台&#xff1b;我这里是安装的其他开…

【C++】18.继承

文章目录 1.继承的概念及定义1.1 继承的概念1.2 继承定义1.2.1定义格式1.2.2继承关系和访问限定符1.2.3继承基类成员访问方式的变化 1.3 继承类模板 2.基类和派生类对象赋值转换3.继承中的作用域3.1 隐藏规则&#xff1a;3.2 考察继承作用域相关选择题 4.派生类的默认成员函数4…

51单片机——8*8LED点阵

LED 点阵的行则为发光二极管的阳极&#xff0c;LED 点阵的列则为发光二极管的阴极 根据 LED 发光二极管导通原理&#xff0c;当阳极为高电平&#xff0c;阴极为低电平则点亮&#xff0c;否则熄灭。 因此通过单片机P0口可控制点阵列&#xff0c;74HC595可控制点阵行 11 脚 SR…

FastDeploy部署paddlecls分类模型(windows)

目录 写在前面 总体步骤 C SDK编译库 方式1&#xff1a;编译安装 方式2&#xff1a;下载预编译库 准备模型、文件、代码和数据 模型文件类型 samples代码 待预测图像 使用 FastDeploy C SDK 将cpp源码编译为exe 编写cpp代码 cpp代码编译exe 运行可执行程序exe 将…

电脑如何无线控制手机?

想在电脑上无线控制手机&#xff0c;需要用到Total Control控制软件&#xff0c;具体步骤如下&#xff1a; 1、首先我们在电脑上安装上控制软件Total Control并打开。 2、开启手机USB调试和ADB仅充电模式。 3、手机电脑均连接上相同局域网。 4、连接(首次使用需要用手机U…

C++ Qt练习项目 QChar功能测试

个人学习笔记 代码仓库 GitCode - 全球开发者的开源社区,开源代码托管平台 新建项目 设计UI 1、拖入group box去掉名字 2、拖入2个LineEdit 3、拖入两个Label 4、拖入两个PushButton 5、点栅格布局 1、拖入GroupBox 2、拖入4个PushButton 3、点栅格布局 1、拖入GroupBo…

QT c++ 样式 设置 标签(QLabel)的渐变色美化

上一篇文章中描述了按钮的纯色&#xff0c;本文描述标签的渐变色美化。 1.头文件 #ifndef WIDGET_H #define WIDGET_H #include <QWidget> //#include "CustomButton.h"#include <QVBoxLayout> #include <QLinearGradient> #include <QLabel…

【C++面向对象——输入输出流】处理二进制文件(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 一、流类库中常用的类及其成员函数 二、标准输入输出及格式控制 三、文件的应用方法&#xff08;二进制文件、文本文件&#xff09; 编程要求 实验步骤 通关代码 测试结果 任务描述 本关任务&#xff1a; 用二进制方式打开指定的…

基于大数据爬虫+Python+数据可视化大屏的慧游数据爬虫与推荐分析系统(源码+论文+PPT+部署文档教程等)

博主介绍&#xff1a;**CSDN毕设辅导第一人、**全网粉丝50W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流 **技术范围&#xff1a;**S…

Linux Shell 脚本编程基础知识篇—awk的条件判断(3)

ℹ️大家好&#xff0c;我是练小杰&#xff0c;今天周五了&#xff0c;又是一周过去了&#x1f606; 本文是有关Linux shell脚本编程的awk命令的条件语句&#xff0c;后续我会不断增加相关内容 ~~ 回顾:【awk字符串函数和内置变量】 更多Linux 相关内容请点击&#x1f449;【Li…