在这里插入图片描述

缓存是把双刃剑。用得好,能让应用秒开,体验飞起;用不好,占满存储空间,用户骂娘。今天咱们就来聊聊,怎么做一个既实用又友好的缓存管理功能。

缓存管理的设计思路

做缓存管理之前,得先想清楚要展示什么、怎么操作。

展示缓存大小是最基本的。用户要知道应用占了多少空间,心里才有数。而且要分类展示:图片缓存多少、文章缓存多少、视频缓存多少。这样用户能有针对性地清理。

清除操作要分级。可以清除单个类型的缓存,也可以一键清除全部。给用户选择的权利,不要强制清除所有缓存。

操作要有确认。清除缓存是不可逆的,必须二次确认。而且要告诉用户会释放多少空间,让用户知道清除的价值。

反馈要及时。清除完成后,要立即更新显示的缓存大小,并提示用户操作成功。不要让用户等待或困惑。

页面整体结构

缓存管理页面用StatelessWidget,因为缓存大小是模拟数据,不需要管理状态:

class CacheSettingsScreen extends StatelessWidget {
  const CacheSettingsScreen({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('缓存管理'),
      ),
      body: ListView(
        children: [
          _buildCacheSizeCard(),
          const Divider(),
          _buildCacheItem('图片缓存', '32.5 MB', Icons.image),
          _buildCacheItem('文章缓存', '8.3 MB', Icons.article),
          _buildCacheItem('视频缓存', '4.4 MB', Icons.video_library),
          const Divider(),
          _buildClearAllButton(),
        ],
      ),
    );
  }
}

ListView包含多个部分:顶部的缓存大小卡片、中间的分类缓存列表、底部的清除全部按钮。用Divider分隔不同区域。

实际项目中,应该用StatefulWidget,因为清除缓存后要更新显示。但咱们这里用模拟数据,就用StatelessWidget简化代码。

缓存大小卡片的设计

顶部的卡片要醒目,让用户一眼就能看到总缓存大小:

Padding(
  padding: const EdgeInsets.all(16),
  child: Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        children: [
          const Icon(
            Icons.storage,
            size: 64,
            color: Colors.blue,
          ),
          const SizedBox(height: 16),
          const Text(
            '45.2 MB',
            style: TextStyle(
              fontSize: 32,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 8),
          Text(
            '当前缓存大小',
            style: TextStyle(
              fontSize: 14,
              color: Colors.grey[600],
            ),
          ),
        ],
      ),
    ),
  ),
)

Card提供了卡片的背景和阴影,让内容有层次感。Padding: 16让卡片不贴边,视觉上更舒服。

Icon用存储图标,size: 64很大,视觉冲击力强。color: Colors.blue用蓝色,表示这是信息展示,不是警告。

Text显示缓存大小,fontSize: 32很大,fontWeight: FontWeight.bold加粗。这是页面最重要的信息,要突出显示。

下面的"当前缓存大小"用小字号、灰色,作为说明文字。

这个卡片的设计很清晰:大图标、大数字、小说明,用户一眼就能看懂。

分类缓存列表的实现

每个缓存类型用ListTile展示:

ListTile(
  leading: const Icon(Icons.image),
  title: const Text('图片缓存'),
  subtitle: const Text('32.5 MB'),
  trailing: TextButton(
    onPressed: () {
      _showClearDialog(context, '图片缓存');
    },
    child: const Text('清除'),
  ),
)

leading是左侧的图标,不同类型用不同图标:图片用Icons.image,文章用Icons.article,视频用Icons.video_library。图标要直观,让用户一眼就知道是什么类型。

title是缓存类型的名称,subtitle是缓存大小。这样信息层次清晰,不会混在一起。

trailing是右侧的清除按钮。用TextButton而不是IconButton,因为"清除"两个字比图标更明确。用户不用猜这个按钮是干什么的。

清除单个缓存的对话框

点击清除按钮,弹出确认对话框:

void _showClearDialog(BuildContext context, String type) {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: Text('清除$type'),
      content: Text('确定要清除$type吗?'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('取消'),
        ),
        TextButton(
          onPressed: () {
            Navigator.pop(context);
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('已清除$type')),
            );
          },
          child: const Text('确定'),
        ),
      ],
    ),
  );
}

titlecontent都用了字符串插值,动态显示缓存类型。这样一个方法就能处理所有类型,不用写三个重复的方法。

actions有两个按钮:取消和确定。取消只是关闭对话框,确定会关闭对话框并显示提示。

ScaffoldMessenger显示SnackBar提示用户操作成功。这个反馈很重要,让用户知道清除完成了。

清除全部缓存的按钮

底部有个醒目的按钮,清除所有缓存:

Padding(
  padding: const EdgeInsets.all(16),
  child: ElevatedButton(
    onPressed: () {
      _showClearAllDialog(context);
    },
    style: ElevatedButton.styleFrom(
      padding: const EdgeInsets.symmetric(vertical: 16),
    ),
    child: const Text('清除全部缓存'),
  ),
)

ElevatedButton而不是TextButton,因为这是主要操作,要突出显示。padding: vertical 16让按钮更高,更容易点击。

Padding: 16让按钮不贴边,和上面的内容有间距。

清除全部缓存的对话框

清除全部缓存的对话框要更详细:

void _showClearAllDialog(BuildContext context) {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('清除全部缓存'),
      content: const Text('确定要清除所有缓存吗?这将释放 45.2 MB 空间。'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('取消'),
        ),
        TextButton(
          onPressed: () {
            Navigator.pop(context);
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(content: Text('已清除全部缓存')),
            );
          },
          child: const Text('确定'),
        ),
      ],
    ),
  );
}

content不仅确认操作,还告诉用户"这将释放 45.2 MB 空间"。这个信息很重要,让用户知道清除的价值,更愿意点确定。

实际计算缓存大小

上面的代码用的是模拟数据,实际项目要真的计算缓存大小。可以用path_provider获取缓存目录:

import 'package:path_provider/path_provider.dart';
import 'dart:io';

Future<int> _getCacheSize() async {
  final cacheDir = await getTemporaryDirectory();
  return _calculateDirectorySize(cacheDir);
}

int _calculateDirectorySize(Directory dir) {
  int totalSize = 0;
  
  try {
    if (dir.existsSync()) {
      dir.listSync(recursive: true, followLinks: false)
          .forEach((FileSystemEntity entity) {
        if (entity is File) {
          totalSize += entity.lengthSync();
        }
      });
    }
  } catch (e) {
    print('计算缓存大小失败: $e');
  }
  
  return totalSize;
}

getTemporaryDirectory获取临时目录,这是Flutter存放缓存的地方。listSync遍历目录下的所有文件,lengthSync获取文件大小,累加起来就是总缓存大小。

格式化缓存大小

缓存大小要格式化成易读的格式:

String _formatBytes(int bytes) {
  if (bytes < 1024) {
    return '$bytes B';
  } else if (bytes < 1024 * 1024) {
    return '${(bytes / 1024).toStringAsFixed(1)} KB';
  } else if (bytes < 1024 * 1024 * 1024) {
    return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
  } else {
    return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
  }
}

根据大小选择合适的单位:小于1KB显示字节,小于1MB显示KB,小于1GB显示MB,否则显示GB。**toStringAsFixed(1)**保留一位小数。

这样显示的缓存大小更友好,用户一眼就能看懂。

清除缓存的实现

真正清除缓存要删除文件:

Future<void> _clearCache() async {
  try {
    final cacheDir = await getTemporaryDirectory();
    if (cacheDir.existsSync()) {
      cacheDir.deleteSync(recursive: true);
      cacheDir.createSync();  // 重新创建空目录
    }
  } catch (e) {
    print('清除缓存失败: $e');
  }
}

deleteSync(recursive: true)递归删除目录下的所有文件。删除后要createSync重新创建目录,否则应用可能出错。

注意这里用了try-catch,因为删除文件可能失败(权限不足、文件被占用等)。失败时不要让应用崩溃,打印错误日志就行。

分类清除缓存

如果要分类清除缓存,需要把不同类型的缓存放在不同目录:

Future<void> _clearImageCache() async {
  final cacheDir = await getTemporaryDirectory();
  final imageDir = Directory('${cacheDir.path}/images');
  
  if (imageDir.existsSync()) {
    imageDir.deleteSync(recursive: true);
    imageDir.createSync();
  }
}

Future<void> _clearArticleCache() async {
  final cacheDir = await getTemporaryDirectory();
  final articleDir = Directory('${cacheDir.path}/articles');
  
  if (articleDir.existsSync()) {
    articleDir.deleteSync(recursive: true);
    articleDir.createSync();
  }
}

应用运行时,把图片缓存到images目录,文章缓存到articles目录。清除时就能分类清除了。

用StatefulWidget管理状态

实际项目要用StatefulWidget,清除缓存后更新显示:

class CacheSettingsScreen extends StatefulWidget {
  const CacheSettingsScreen({super.key});

  
  State<CacheSettingsScreen> createState() => _CacheSettingsScreenState();
}

class _CacheSettingsScreenState extends State<CacheSettingsScreen> {
  int _totalSize = 0;
  int _imageSize = 0;
  int _articleSize = 0;
  int _videoSize = 0;
  bool _isLoading = true;
  
  
  void initState() {
    super.initState();
    _loadCacheSizes();
  }
  
  Future<void> _loadCacheSizes() async {
    setState(() {
      _isLoading = true;
    });
    
    _totalSize = await _getCacheSize();
    _imageSize = await _getImageCacheSize();
    _articleSize = await _getArticleCacheSize();
    _videoSize = await _getVideoCacheSize();
    
    setState(() {
      _isLoading = false;
    });
  }
  
  Future<void> _clearImageCache() async {
    await _clearImageCacheFiles();
    await _loadCacheSizes();  // 重新加载缓存大小
    
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('已清除图片缓存')),
      );
    }
  }
}

_isLoading控制加载状态,加载时显示进度条。_loadCacheSizes计算所有缓存大小。清除缓存后调用**_loadCacheSizes**重新加载,显示的数字就会更新。

mounted检查Widget是否还在树上,避免清除缓存后页面已经关闭,调用setState报错。

加载状态的显示

计算缓存大小可能需要时间,要显示加载状态:


Widget build(BuildContext context) {
  if (_isLoading) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('缓存管理'),
      ),
      body: const Center(
        child: CircularProgressIndicator(),
      ),
    );
  }
  
  return Scaffold(
    appBar: AppBar(
      title: const Text('缓存管理'),
    ),
    body: ListView(
      children: [
        _buildCacheSizeCard(),
        // ...
      ],
    ),
  );
}

加载时显示进度条,加载完显示内容。这样用户不会觉得应用卡住了。

清除缓存的进度提示

清除缓存也可能需要时间,可以显示进度对话框:

Future<void> _clearAllCache() async {
  // 显示进度对话框
  showDialog(
    context: context,
    barrierDismissible: false,
    builder: (context) => const AlertDialog(
      content: Row(
        children: [
          CircularProgressIndicator(),
          SizedBox(width: 16),
          Text('正在清除缓存...'),
        ],
      ),
    ),
  );
  
  // 清除缓存
  await _clearCacheFiles();
  await _loadCacheSizes();
  
  // 关闭进度对话框
  if (mounted) {
    Navigator.pop(context);
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('已清除全部缓存')),
    );
  }
}

barrierDismissible: false防止用户点击外部关闭对话框。清除完成后手动关闭对话框,显示成功提示。

缓存策略的优化

除了清除缓存,还可以优化缓存策略。

设置缓存大小限制

class CacheManager {
  static const int maxCacheSize = 100 * 1024 * 1024;  // 100MB
  
  Future<void> checkAndClearCache() async {
    final size = await _getCacheSize();
    if (size > maxCacheSize) {
      await _clearOldestCache();
    }
  }
  
  Future<void> _clearOldestCache() async {
    final cacheDir = await getTemporaryDirectory();
    final files = cacheDir.listSync(recursive: true)
        .whereType<File>()
        .toList();
    
    // 按修改时间排序
    files.sort((a, b) => 
        a.lastModifiedSync().compareTo(b.lastModifiedSync()));
    
    // 删除最旧的文件,直到缓存小于限制
    int currentSize = await _getCacheSize();
    for (final file in files) {
      if (currentSize <= maxCacheSize) break;
      
      final fileSize = file.lengthSync();
      file.deleteSync();
      currentSize -= fileSize;
    }
  }
}

设置缓存过期时间

Future<void> _clearExpiredCache() async {
  final cacheDir = await getTemporaryDirectory();
  final now = DateTime.now();
  final expireDuration = const Duration(days: 7);
  
  cacheDir.listSync(recursive: true)
      .whereType<File>()
      .forEach((file) {
    final lastModified = file.lastModifiedSync();
    if (now.difference(lastModified) > expireDuration) {
      file.deleteSync();
    }
  });
}

按使用频率清除

class CacheItem {
  final String path;
  int accessCount = 0;
  DateTime lastAccess = DateTime.now();
}

Future<void> _clearLeastUsedCache() async {
  // 根据访问次数和最后访问时间计算分数
  // 删除分数最低的缓存
}

图片缓存的特殊处理

图片缓存通常用cached_network_image插件,它有自己的缓存管理:

import 'package:cached_network_image/cached_network_image.dart';

Future<void> _clearImageCache() async {
  await DefaultCacheManager().emptyCache();
  
  if (mounted) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('已清除图片缓存')),
    );
  }
}

Future<int> _getImageCacheSize() async {
  final cacheDir = await DefaultCacheManager().getDirectory();
  return _calculateDirectorySize(cacheDir);
}

DefaultCacheManager是cached_network_image的缓存管理器,emptyCache清除所有图片缓存。

缓存统计信息

可以显示更详细的缓存统计:

class CacheStats {
  final int totalSize;
  final int fileCount;
  final DateTime oldestFile;
  final DateTime newestFile;
  
  CacheStats({
    required this.totalSize,
    required this.fileCount,
    required this.oldestFile,
    required this.newestFile,
  });
}

Future<CacheStats> _getCacheStats() async {
  final cacheDir = await getTemporaryDirectory();
  final files = cacheDir.listSync(recursive: true)
      .whereType<File>()
      .toList();
  
  int totalSize = 0;
  DateTime? oldest;
  DateTime? newest;
  
  for (final file in files) {
    totalSize += file.lengthSync();
    final modified = file.lastModifiedSync();
    
    if (oldest == null || modified.isBefore(oldest)) {
      oldest = modified;
    }
    if (newest == null || modified.isAfter(newest)) {
      newest = modified;
    }
  }
  
  return CacheStats(
    totalSize: totalSize,
    fileCount: files.length,
    oldestFile: oldest ?? DateTime.now(),
    newestFile: newest ?? DateTime.now(),
  );
}

显示这些统计信息,让用户更了解缓存情况。

自动清理缓存

可以在应用启动时自动清理过期缓存:

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    _autoCleanCache();
    
    return MaterialApp(
      home: MainScreen(),
    );
  }
  
  Future<void> _autoCleanCache() async {
    final prefs = await SharedPreferences.getInstance();
    final lastClean = prefs.getInt('lastCacheClean') ?? 0;
    final now = DateTime.now().millisecondsSinceEpoch;
    
    // 7天清理一次
    if (now - lastClean > 7 * 24 * 60 * 60 * 1000) {
      await _clearExpiredCache();
      await prefs.setInt('lastCacheClean', now);
    }
  }
}

常见问题

缓存大小计算很慢:可以在后台线程计算,或者缓存计算结果。

清除缓存后应用出错:可能是删除了正在使用的文件。清除前要确保文件没有被占用。

缓存大小不准确:可能是有些缓存不在临时目录。要检查所有可能的缓存位置。

写在最后

缓存管理看起来简单,但要做好不容易。要准确计算缓存大小,要安全地清除缓存,要及时更新显示,要给用户友好的反馈。

最重要的是用户体验。操作要简单,反馈要及时,信息要清晰。这些细节做好了,用户才会觉得应用专业、可靠。

代码写完了,记得多测试。不同大小的缓存显示对不对?清除缓存会不会出错?清除后显示更新了吗?这些场景都要测试到。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

在这里你可以找到更多Flutter开发资源,与其他开发者交流经验,共同进步。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐