Flutter 实战避坑:相册页二次刷新被清空、全屏图片拉伸、ML Kit 人脸检测最小尺寸问题

张开发
2026/4/5 19:56:38 15 分钟阅读

分享文章

Flutter 实战避坑:相册页二次刷新被清空、全屏图片拉伸、ML Kit 人脸检测最小尺寸问题
摘要本文复盘了 Flutter 相册类 App 开发中遇到的 3 个典型问题人物页首屏正常但二次刷新后被清空、全屏图片预览时发生拉伸变形以及 ML Kit 人脸检测偶发报错InputImage width and height should be at least 32!。文章结合实际代码从现象、排查、根因到修复方案进行完整分析并总结了 Flutter 状态管理、图片解码链路和端侧 AI 输入校验中的常见踩坑点。关键词FlutterML Kit人脸检测图片拉伸BoxFit状态覆盖异步刷新相册应用端侧 AIBug 复盘前言最近在做 Flutter 相册类 App 时连续遇到了 3 个很典型的小问题人物页首屏有内容过一秒却被清空图片进入全屏预览后人物脸部被莫名拉长ML Kit 人脸检测偶发报错InputImage width and height should be at least 32!这 3 个问题看起来都不算“致命 bug”主流程甚至还能继续跑但它们都非常影响用户体验也很容易长期潜伏在线上。更关键的是它们分别涉及Flutter 页面状态管理图片解码与显示链路端侧 AI 输入合法性校验这篇文章就把这 3 个问题做一次完整复盘记录现象、排查过程、根因分析以及最终修复方案希望能给做 Flutter 相册、图片浏览和端侧 AI 应用的同学一些参考。Bug 1Flutter 人物页首屏正常二次刷新后列表被清空1. 问题现象进入“人物”页时页面一开始显示正常共 17 张图片12 个相关标签图片网格正常渲染但过一秒后页面突然变成0 张图片0 个相关标签出现空状态提示这说明问题并不是“图片加载失败”而是页面状态被后续一次刷新覆盖成了空结果。2. 初步排查方向一开始比较容易怀疑这些问题face cluster 索引还没准备好有未完成照片导致 live query 查空页面发起了两次请求后返回的空结果覆盖了前面正确结果这些方向都合理但继续往下查之后发现真正的问题比想象中更直接。3. 根因分析人物弹层的首屏数据和后续刷新数据使用的不是同一套查询口径。外层标签浏览页在构建人物簇时取的是最近1200张照片但人物弹层打开后又会通过watchLazy(fireImmediately: true)触发一次刷新而这次刷新只查询最近800张照片。这就导致了一个非常典型的问题首屏先拿到了基于1200张数据计算出来的人物结果所以页面一开始有图后续fireImmediately立刻触发刷新刷新逻辑只看最近800张如果这 17 张图不在最新 800 张里刷新结果就变成空UI 被空结果直接覆盖本质上就是首屏和刷新使用了两套不同的数据窗口。4. 修复方案修复方式并不复杂但非常关键将标签总览和人物弹层统一成同一个照片来源不再出现“外层按 1200 查、弹层按 800 查”的情况通过共享 loader保证首屏和刷新口径一致修复前// 标签页 limit(1200) // 弹层刷新 limit(800)修复后FutureListPhotoEntity _loadAlbumTagBrowserSourcePhotos() { return PhotoService().isar.photoEntitys .where() .sortByTimestampDesc() .limit(1200) .findAll(); }然后让外层和弹层都走同一个入口。5. 经验总结这类问题的关键不在于“异步刷新”本身而在于异步刷新前后是否使用了完全一致的数据口径。如果不是就很容易出现这种非常迷惑的现象首屏有内容一秒后没了用户误以为数据丢失或页面异常所以只要页面存在“首屏加载 自动刷新”的结构就一定要优先检查前后两次查询条件是不是完全一致。Bug 2Flutter 全屏图片预览时被拉伸变形1. 问题现象缩略图看起来完全正常但点进全屏预览后人物脸部明显被拉长看起来像是被强行拉成了某个固定比例。遇到这种问题第一反应通常会怀疑使用了BoxFit.fill容器被写死了宽高比EXIF 方向信息异常导致宽高错乱但这次继续排查后发现根因并不在fit。2. 排查过程全屏查看器里的代码其实没有问题用的是PathImage(path: path, fit: BoxFit.contain)BoxFit.contain理论上会保持原始比例不应该产生拉伸。既然显示层没有明显问题那么问题就只能继续往更底层的“图片解码阶段”查。3. 根因分析问题出在图片组件PathImage的“智能缓存”逻辑上。它会根据当前布局约束同时给Image.file传入cacheWidthcacheHeight如果这两个值同时被指定Flutter 在解码阶段就可能按照这两个缓存尺寸去缩放位图。这里有一个非常容易被忽略的点BoxFit.contain只负责布局阶段如何摆放图片不会修正前面解码阶段已经发生的非等比缩放。所以最终效果就变成了图片先在解码阶段被按视口宽高缩成错误比例然后再通过BoxFit.contain去显示表面看起来像是“显示层拉伸了”实际上是“解码阶段就已经歪了”4. 修复方案对于全屏查看器最稳妥的方式不是继续走智能缓存而是保留BoxFit.contain关闭智能缓存尺寸提示让全屏图按原始比例解码显示修复后代码final image PathImage( path: path, fit: BoxFit.contain, enableSmartCache: false, );5. 经验总结这类问题特别容易误判成BoxFit.fillHero 动画变形宽高比元数据异常但实际上图片解码缓存策略本身也会影响最终显示比例。一个非常重要的经验是当你怀疑图片被拉伸时不仅要检查布局层还要检查解码层。很多时候问题并不是“怎么显示”而是“图片在显示之前就已经被错误缩放”。Bug 3ML Kit 人脸检测报错InputImage width and height should be at least 321. 问题现象日志中偶发出现这样的报错PlatformException(FaceDetectorError, com.google.mlkit.common.MlKitException: InputImage width and height should be at least 32!)这段报错的意思很明确送进 ML Kit 人脸检测器的输入图像宽和高至少有一边小于 32 像素。2. 这不是“模型挂了”这类报错特别容易让人误会成ML Kit 不稳定模型加载失败某些照片文件损坏但这次问题本质上都不是这些而是前处理阶段没有对输入尺寸做兜底。3. 排查结果人脸检测这条链路并不是直接拿列表缩略图去跑检测而是先根据原图生成一个辅助分析文件再把这个文件传给FaceDetector.processImage()代码大致如下analysisFile await _createAuxiliaryAnalysisFile(prepared.file, photo.id); final inputImage InputImage.fromFile(analysisFile); final faces await faceDetector.processImage(inputImage);问题在于这里之前没有做任何尺寸检查。也就是说只要某张图片本身特别小或者某一边特别窄就会把不满足要求的图片直接送进 ML Kit最终抛出异常。4. 修复方案修复原则并不是“强行放大图片再去跑”而是先读取分析图的实际尺寸如果任一边小于 32则直接跳过人脸检测但不影响标签、OCR、caption 等其他 AI 分析流程修复后逻辑final analysisDimensions await _readImageDimensions(analysisFile); final canRunFaceDetection analysisDimensions ! null analysisDimensions.$1 32 analysisDimensions.$2 32; final faces canRunFaceDetection ? await faceDetector.processImage(inputImage) : const Face[];同时补充诊断日志方便后续排查debugPrint( 跳过人脸检测 photoId${photo.id} dbSize${photo.width}x${photo.height} analysisSize$sizeLabel );5. 为什么这样处理更合理因为这里的问题不是“整张照片都不能分析”而只是这张图不适合做人脸检测。如果因为人脸检测失败就让整条 AI 分析链路中断就会造成更大的影响例如标签结果丢失OCR 失败caption 缺失数据库存储状态不完整所以更合理的策略应该是人脸检测失败或不适用只影响 face 结果不影响整张照片的其他分析流程。这本质上也是一种很重要的工程思路局部能力失败不应该拖垮整条主链路。这 3 个 Bug 的共同点这次修掉的 3 个问题虽然分别属于不同模块但本质上都可以归类为口径不一致 边界条件未处理。1. 页面状态口径不一致首屏使用1200刷新使用800导致异步刷新覆盖正确结果2. 图片解码口径不一致布局层使用contain解码层却按视口宽高同时设置了双边缓存提示导致位图先被缩歪3. AI 输入边界条件缺少兜底没有检查最小输入尺寸极端小图直接把人脸检测流程打爆适用场景如果你正在做下面这些项目这篇文章里的问题都很容易遇到Flutter 相册或图库类应用图片预览、缩略图、全屏查看器本地 AI 分析链路人脸、OCR、标签、caption使用 ML Kit 做端侧视觉分析需要处理大量异步刷新和页面状态同步的 App最后的经验做 Flutter 本地 AI 这类应用时我越来越觉得真正难的不是“把功能做出来”而是让这些功能在各种边界条件下依然稳定。这次的 3 个小 bug 给我的经验也很明确异步刷新前后一定要统一数据口径图片显示问题不能只看BoxFit还要检查解码参数AI 模块的前处理一定要加输入合法性保护很多线上问题都不是“核心逻辑完全错了”而是主流程是对的但边界没有兜住。而工程质量往往就体现在这些边角处。结语如果你也在做 Flutter 相册、图片浏览或者端侧 AI 分析相关项目这几个坑其实都很有代表性。看起来只是“小 bug”但修完之后对用户体验和系统稳定性的提升往往非常明显。后面如果有机会我也会继续整理这些方向的实战内容比如Flutter 图片显示链路排查思路ML Kit / OCR / Caption 协同分析设计相册类 App 的状态流与异步刷新治理欢迎交流也欢迎关注后续更新。

更多文章