前言

最近在维护我的 Flutter 智能相册项目 Memoria 时,我做了一次比较完整的工程化整理。

这个项目本身并不是一个简单的相册 Demo,而是一个集成了:

  • 系统相册扫描;

  • Isar 本地数据库;

  • ObjectBox 向量索引;

  • MobileCLIP 本地视觉模型;

  • OCR;

  • LLM 图片描述与故事生成;

  • 事件聚类;

  • 地点解析;

  • 语义检索;

的智能相册应用。

随着项目功能越来越多,代码复杂度也在快速上升。功能能跑起来只是第一步,真正进入项目中后期后,会遇到很多更“工程化”的问题:

本地运行日志会不会泄露隐私?
中文日志为什么突然变成乱码?
生成代码到底该不该提交?
Flutter 升级后 Impeller 配置是否还兼容?
flutter analyze 里大量 warning/info 怎么收口?
哪些问题应该马上处理,哪些应该留作技术债?

这次我针对这些问题做了一轮集中治理。最终提交为:

f64bcd4 清理运行日志与分析配置

本次变更涉及 15 个文件,整体 diff 大约为 +1103 / -1099。其中一部分行数来自重新纳入版本控制的 Isar 生成文件,所以实际业务代码改动并没有看起来那么夸张。


一、这次为什么要做工程化整理?

在真机调试过程中,我发现项目已经可以正常构建、安装、运行,AI 分析、相册扫描、事件聚类等主链路也能跑起来。

但是与此同时,也暴露出几个不适合继续拖延的问题:

1. run_output.txt 出现在 Git 未跟踪文件中
2. 部分中文日志出现 mojibake 乱码
3. .gitignore 对 *.g.dart 的规则不够准确
4. Android Manifest 和启动脚本仍然显式关闭 Impeller
5. flutter analyze 噪音较多,生成文件 warning 干扰判断
6. withOpacity 已经出现 deprecated 提示

这些问题单独看都不算大,但放在一个长期维护的项目里,会逐渐变成协作成本和稳定性风险。

所以这次的目标不是开发新功能,而是做一次“项目卫生清理”:

让 Git 状态更干净
让日志更安全
让中文文案不再乱码
让生成代码规则更明确
让 Flutter 新版本兼容性更好
让 analyze 输出更有参考价值

二、处理本地运行日志:避免隐私泄露

第一个要处理的是 run_output.txt

Flutter 真机运行时,日志里可能包含大量敏感信息,例如:

设备 ID
本地路径
相册扫描数量
照片地理位置
经纬度
高德逆地理返回结果
LLM 请求状态
图片语义标签
调试接口地址

这些内容非常适合本地排查问题,但绝对不应该提交到 Git。

因此我做了两件事。

第一,删除当前未跟踪的本地日志文件:

Remove-Item .\run_output.txt -ErrorAction SilentlyContinue

第二,把运行日志加入 .gitignore

# Local debug / run logs
run_output.txt
*.log
flutter_*.log
analyze_output.txt

这样以后即使再次执行类似命令:

flutter run -d <device-id> --dart-define-from-file=config/profiles/dev.json 2>&1 | Tee-Object -FilePath .\run_output.txt

也不会污染 Git 工作区。

这个改动看起来很小,但它解决的是一个很实际的问题:

调试日志可以本地保存,但不能进入版本库。

尤其是智能相册项目,照片地址、OCR 内容、AI 标签、用户相册结构都属于比较敏感的信息,日志治理必须尽早做。


三、修复中文乱码:从“能看懂”到“能维护”

第二个问题是中文乱码。

项目中有一些日志原本应该是正常中文,例如:

基础数据同步完成
安全重建完成
读取系统缩略图失败

但在 Windows PowerShell、Git 合并、脚本写入等多次操作后,部分文本变成了类似:

鉁?鍩虹鏁版嵁鍚屾瀹屾垚
鈿狅笍 璇诲彇绯荤粺缂╃暐鍥惧け璐

这类乱码通常是 UTF-8 内容被按 GBK/CP936 错误解码后,又被重新写回文件导致的。

这次我重点修了三类乱码。

1. 运行时 debug 日志

比如相册扫描相关日志,从乱码恢复成可读中文:

debugPrint(
  '基础数据同步完成: 删除=${plan.removedCount} 入库=${plan.built.insertedCount} '
  '无GPS=${plan.built.insertedNoGps} 无效时间=${plan.built.skippedInvalidTime} '
  '非相机=${plan.built.skippedNonCamera} 截图=${plan.built.skippedScreenshot}',
);

安全重建日志也改成了清晰的表达:

debugPrint(
  '安全重建完成: 清空旧数据=${plan.totalBefore} 入库=${plan.built.insertedCount} '
  '无GPS=${plan.built.insertedNoGps} 无效时间=${plan.built.skippedInvalidTime} '
  '非相机=${plan.built.skippedNonCamera} 截图=${plan.built.skippedScreenshot}',
);

这类日志对排查相册扫描问题非常重要。比如用户反馈“照片没扫出来”,我们需要快速看到:

入库多少张
跳过多少张
是否无 GPS
是否因为无效时间被过滤
是否因为截图策略被过滤

如果日志是乱码,后续排查会非常痛苦。


2. AI 分析链路日志

AI 处理链路中也有乱码,例如读取缩略图失败、写入分析结果失败、读取图片尺寸失败等。

修复后类似:

debugPrint('读取系统缩略图失败 photoId=${photo.id}: $error');
debugPrint(
  'AI 写入照片分析结果失败 photoId=${result.profile.photoId} error=$error',
);
debugPrint('读取分析图片尺寸失败 path=${imageFile.path}: $error');

这些日志覆盖的是 MobileCLIP / 图片解码 / Isar 写入 / ObjectBox 向量写入等关键路径。

在本地 AI 相册项目中,AI 分析链路往往是最复杂、最容易出现边界问题的部分:

图片文件不存在
系统缩略图读取失败
原图过大导致解码慢
图片尺寸异常
向量写入失败
数据库事务失败

因此日志必须保持可读。


3. LLM Prompt 乱码

这次比较关键的发现是:有一段多模态图片描述的 system prompt 也变成了乱码。

原本这段 prompt 的作用是约束视觉模型:

const systemText = '你是一个谨慎的中文图片描述助手。只能描述图中可见事实,不要脑补职业、关系、剧情和身份。';

这不是普通注释,也不是普通 debug 日志,而是会直接传给 LLM 的运行时 prompt。

如果它变成乱码,模型收到的系统指令就会失效,可能导致:

图片描述质量下降
模型乱编人物关系
生成不存在的剧情
对照片内容产生幻觉

所以这类乱码优先级非常高,必须立刻修。

这也提醒我:编码问题不只是显示问题,某些乱码会直接影响 AI 输出质量。


四、修正 .gitignore:让 Isar 生成文件规则更明确

Flutter + Isar 项目通常会生成大量 .g.dart 文件。

问题在于,很多项目会在 .gitignore 里写:

*.g.dart

这对普通 JSON 序列化生成文件可能没问题,但对 Isar 来说就要谨慎。

因为 Isar 的 schema 生成文件和实体模型强相关。如果团队成员拉代码后缺少这些文件,可能会遇到:

编译失败
schema 不一致
实体方法缺失
本地生成结果不一致

这次我做了规则调整:保留通用忽略规则,同时为 Isar 实体生成文件加例外。

类似:

*.g.dart

!lib/models/entity/*.g.dart
!lib/models/entity/**/*.g.dart

这次 .gitignore 规则调整后,之前被忽略的几个 Isar .g.dart 文件显露出来,并被纳入版本控制。

这一步的技术点在于:

生成文件不是一律提交或一律忽略,而要看它是否属于项目 schema 的一部分。

对于这个项目来说,Isar 实体生成文件属于协作一致性的一部分,应该明确纳入版本管理。


五、移除 Impeller opt-out:适配 Flutter 后续版本

真机运行日志中出现过 Flutter 的提示:

Impeller opt-out deprecated

说明项目中仍然显式关闭了 Impeller。

我检查后发现,Impeller opt-out 出现在两个地方:

AndroidManifest.xml
launch.ps1
launch.sh

启动脚本里原来类似这样:

flutter run \
  -d "${DEVICE}" \
  --no-enable-impeller \
  --dart-define-from-file="${PROFILE_FILE}"

我把 --no-enable-impeller 移除了:

flutter run \
  -d "${DEVICE}" \
  --dart-define-from-file="${PROFILE_FILE}"

Android Manifest 中对应的关闭配置也一并移除。

这一步的意义是:

不继续依赖 Flutter 即将废弃的 opt-out 选项
让项目尽早适配默认渲染路径
避免未来升级 Flutter 时突然爆雷

当然,移除 opt-out 后不能只看编译是否通过,还需要重点验证:

相册缩略图是否正常显示
页面切换是否闪烁
图片预览是否黑屏
动画是否掉帧
低端机是否有渲染异常

这次先完成配置层面的收口,后续可以再单独做 Impeller 真机兼容性专项测试。


六、迁移 deprecated API:withOpacity -> withValues

Flutter 新版本中,Color.withOpacity 已经逐渐不推荐使用,Analyzer 会给出 deprecated 提示。

项目里部分 UI 代码原来是:

Colors.black.withOpacity(0.6)

这次统一改成:

Colors.black.withValues(alpha: 0.6)

例如静态滤镜效果中:

RadialGradient(
  colors: [
    Colors.transparent,
    Colors.black.withValues(alpha: 0.6),
  ],
)

发光、阴影、半透明背景等位置也做了类似替换。

这个改动不影响业务逻辑,但有两个好处:

减少 analyzer deprecated 噪音
提升代码对 Flutter 新版本的兼容性

在长期项目里,这类 API 迁移最好小步完成,不要等 deprecated warning 堆积到几十上百个后再集中处理。


七、优化 Analyzer 配置:降低噪音,提高有效性

这次我还调整了 analysis_options.yaml

主要目标不是“强行让 analyze 零 warning”,而是让 analyze 输出更有参考价值。

1. 排除生成文件噪音

生成文件里的 warning 通常不是业务代码问题,比如 Isar/ObjectBox 的 generated code 可能触发某些 experimental 或 lint 提示。

这类内容如果混在业务 warning 里,会干扰判断。

因此我把生成文件从 analyzer 中排除,让分析重点回到人工维护的业务代码。

2. 暂时关闭 avoid_print

项目里目前仍然有不少用于调试的 print,尤其是 AI、LLM、故事生成、事件解析等链路。

理想状态当然是统一 logger,但这是一次较大的重构,涉及:

日志级别
脱敏策略
debug/release 行为
模块标记
异步日志
文件输出

这次我的目标是处理 1、4、6、7、8 这些明确任务,并没有展开“全项目 logger 迁移”。

因此我先在 lint 层面对 avoid_print 做降噪,避免 flutter analyze 被大量既有调试日志淹没。

这是一种阶段性取舍:

当前先让 analyze 能暴露真正值得处理的问题
后续再单独做 logger 体系化治理

八、验证:测试通过,Analyze 可正常退出

工程化改动最怕“看似只改配置,实际破坏构建”。

所以提交前我做了两类验证。

1. 测试集合通过

运行项目测试脚本:

tool\run_test_suite.ps1

结果:

39 个测试通过

说明这次整理没有破坏现有测试集合。

2. flutter analyze 正常退出

运行:

flutter analyze --no-pub --no-fatal-infos --no-fatal-warnings

结果可以正常退出。

当前仍然有 66 个既有 warning/info,主要集中在:

未使用代码
未使用 import
空值判断
部分 story/video/offscreen 相关字段
历史调试逻辑

这些问题没有在本次任务里继续扩大处理。

我认为这是合理的,因为工程治理也要控制边界:

本次处理日志、安全、乱码、生成文件、Impeller、Analyzer 降噪
未使用代码和 logger 迁移单独拆后续任务

九、最终提交范围

本次提交涉及的核心文件包括:

.gitignore
analysis_options.yaml
android/app/src/main/AndroidManifest.xml
launch.ps1
launch.sh
lib/effects/static_filters.dart
lib/service/ai_service_input.dart
lib/service/ai_service_photo_processing.dart
lib/service/llm_service.dart
lib/service/llm_service_completion.dart
lib/service/photo_service_scan.dart
lib/view/pages/digital_album_book_page.dart
lib/view/pages/event_detail_page.dart
lib/view/pages/home_page.dart
lib/view/pages/publish_page.dart

最终提交:

f64bcd4 清理运行日志与分析配置

处理内容可以概括为:

1. 忽略本地调试日志 run_output.txt
2. 修复运行时中文乱码
3. 修复影响 LLM 的乱码 system prompt
4. 明确 Isar .g.dart 生成文件追踪规则
5. 移除 Impeller opt-out
6. 替换 deprecated withOpacity
7. 调整 analyzer 配置,排除生成文件噪音
8. 跑测试和 analyze 验证

十、这次工作的技术含量在哪里?

从外部看,这次没有新增一个“用户可见大功能”。

但从工程角度看,这类工作非常重要。

1. 它处理的是项目长期维护成本

如果不处理 .gitignore 和生成文件规则,团队后续可能会反复遇到:

别人拉代码编译失败
本地生成文件不一致
schema 文件缺失
Git 状态不干净

2. 它处理的是安全边界

run_output.txt 这类日志文件很容易被忽略,但里面可能包含:

真实地理位置
设备信息
照片语义
接口返回
模型调试输出

智能相册项目尤其要重视这一点。

3. 它处理的是 AI 输出质量

LLM system prompt 乱码不是小问题。

如果 prompt 乱码,模型约束就会失效,直接影响图片 caption、故事生成和内容安全。

这说明工程问题和 AI 质量之间并不是割裂的。

4. 它处理的是 Flutter 版本演进风险

Impeller opt-out、withOpacity deprecated 这类问题今天不修,未来 Flutter 升级时可能集中爆发。

提前收口能降低后续升级成本。

5. 它让 analyzer 更有价值

一个项目如果 analyze 输出几百条噪音,团队往往会慢慢无视它。

这次通过排除生成文件、处理 deprecated API、降低阶段性噪音,让 analyzer 重新变得可用。


十一、总结

这次工程化整理让我更明显地感受到:
写代码不只是实现功能,还包括让项目长期可维护、可协作、可升级、可排查。

这次我主要完成了:

日志安全治理
中文乱码修复
AI prompt 修复
生成文件规则修正
Impeller 配置升级
Flutter deprecated API 清理
Analyzer 降噪
测试验证
Git 提交与推送

这些工作不一定像新功能一样“看得见”,但它们决定了项目能不能继续稳定迭代。

对于一个 Flutter 智能相册项目来说,后续还会继续面临:

本地 AI 推理性能
相册权限兼容
图片解码内存压力
ObjectBox 向量索引演进
LLM 日志脱敏
统一 logger
Flutter 新版本适配

所以这次整理不是终点,而是一次阶段性收口。

我的经验是:

项目越复杂,越要定期做工程化清理。否则功能越堆越多,后面每一次开发都会被历史问题拖慢。

下一步,我计划继续处理剩余的 warning/info,并单独设计一套统一日志体系,把 print/debugPrint、隐私脱敏、debug/release 输出策略统一收口。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐