一键去重,释放存储空间。
纯本地、轻量、高精度的安卓图片文件去重工具。
102 个 Kotlin 源文件 · 3 个主页面 · MVVM + Clean Architecture
PhotoCleaner(清图大师)是一款完全离线的 Android 图片去重应用。通过五层感知哈希算法流水线(dHash → LSH → Union-Find → pHash → ORB)精准识别完全重复和高度相似的图片,帮助用户安全清理冗余文件,释放存储空间。
| 特性 | 说明 |
|---|---|
| 🔒 纯本地运算 | 所有扫描、对比、删除操作均在本地完成,不上传任何数据 |
| ⚡ 高速扫描 | 10000 张图片 ≤ 8 秒出首屏结果,50000 张 ≤ 90 秒(中端机) |
| 🎯 高精度识别 | 完全重复 100%,相似图片 ≥ 98%,链式误分组率 ≤ 1% |
| 🛡️ 安全回收站 | 删除的文件进入回收站,30 天内可恢复,过期自动清理 |
| 📱 广泛适配 | Android 8.0 ~ 14+,分区存储完整适配,支持折叠屏 |
| 🌡️ 智能降级 | 根据设备性能自动调节并发数与算法档位,高温/低内存自动降级 |
graph TB
subgraph UI["UI 层 (Jetpack Compose + Material 3)"]
direction LR
Home[首页<br/>扫描/检测/结果三合一]
Recycle[回收站]
Settings[设置<br/>可折叠分组]
Detail[详情页]
end
subgraph ViewModel["ViewModel 层 (Hilt)"]
HomeVM[HomeViewModel]
RecycleVM[RecycleBinViewModel]
SettingsVM[SettingsViewModel]
DetailVM[DetailViewModel]
end
subgraph UseCase["UseCase 层"]
ScanUC[ScanImageUseCase]
IncrementalUC[IncrementalScanUseCase]
DetectUC[DetectDuplicateUseCase]
DeleteUC[DeleteImageUseCase]
RestoreUC[RestoreImageUseCase]
CheckUpdateUC[CheckUpdateUseCase]
GetSettingsUC[GetSettingsUseCase]
end
subgraph Repository["Repository 层"]
ImageRepo[ImageRepository]
FileOp[FileOperator]
UpdateRepo[AppUpdateRepository]
end
subgraph DataSource["数据源层"]
MediaStore[(MediaStore)]
RoomDB[(Room Database)]
DataStore[(DataStore)]
OkHttp[OkHttp / Update API]
end
Home --> HomeVM
Recycle --> RecycleVM
Settings --> SettingsVM
Detail --> DetailVM
HomeVM --> ScanUC
HomeVM --> DetectUC
HomeVM --> DeleteUC
DetailVM --> DeleteUC
RecycleVM --> RestoreUC
SettingsVM --> GetSettingsUC
ScanUC --> ImageRepo
DetectUC --> ImageRepo
DeleteUC --> FileOp
RestoreUC --> FileOp
ImageRepo --> MediaStore
ImageRepo --> RoomDB
FileOp --> RoomDB
UpdateRepo --> OkHttp
GetSettingsUC --> DataStore
| 底部导航栏 | 功能 |
|---|---|
| 🏠 首页 | 扫描图片 → 检测重复 → 展示分组结果(三合一),支持左右滑动切换页面 |
| 🗑️ 回收站 | 查看已删除图片、恢复、永久删除 |
| ⚙️ 设置 | 可折叠分组(扫描/去重/删除/升级/通用/关于),跟随系统语言 |
- 页面切换:底部导航栏切换 + 左右滑动均支持,带滑动过渡动画
- 首次启动流程:请求存储权限 → 同意隐私政策/服务条款 → 进入首页
- 用户信息安全:扫描完成不自动去重,需用户手动点击"开始检测重复图片"
graph TD
App["app<br/>(主应用模块<br/>首页集成扫描/检测/结果)"]
CoreCommon["core:common<br/>(基类/工具/常量/模型)"]
CoreDB["core:database<br/>(Room DB / DAO)"]
Scanner["feature:scanner<br/>(MediaStore扫描)"]
Dup["feature:duplicate<br/>(去重算法)"]
FileOps["feature:fileops<br/>(文件操作/回收站)"]
AppUpdate["feature:appupdate<br/>(应用升级)"]
Settings["feature:settings<br/>(设置页面)"]
App --> Scanner
App --> Dup
App --> FileOps
App --> AppUpdate
App --> Settings
App --> CoreDB
App --> CoreCommon
Scanner --> CoreCommon
Scanner --> CoreDB
Dup --> CoreCommon
FileOps --> CoreCommon
FileOps --> CoreDB
AppUpdate --> CoreCommon
Settings --> CoreCommon
Settings --> FileOps
CoreDB --> CoreCommon
classDef core fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
classDef feature fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
classDef app fill:#fff3e0,stroke:#f57c00,stroke-width:2px
class CoreCommon,CoreDB core
class Scanner,Dup,FileOps,AppUpdate,Settings feature
class App app
| 层级 | 技术选型 |
|---|---|
| 语言 | 100% Kotlin |
| UI | Jetpack Compose + Material 3 + Navigation Compose |
| 架构 | MVVM + Clean Architecture + 多模块 |
| 异步 | Kotlin Coroutines + Flow(流式流水线) |
| DI | Hilt (@Singleton / @HiltViewModel / @HiltWorker) |
| 数据库 | Room 2.6+(WAL 模式,5 实体,4 DAO) |
| 图片加载 | Coil 2.5+(Compose 集成) |
| 网络 | OkHttp 4.12(断点续传、进度回调) |
| 任务调度 | WorkManager(回收站每日自动清理) |
| 构建 | Gradle 8.5 + Kotlin DSL + KSP |
flowchart TB
Input["输入: List<ImageItem>"] --> L1
subgraph L1["Layer 1 · 预过滤 (O(n))"]
Bucket["大小分桶 (log2) + 宽高比分桶<br/>过滤 95%+ 不相关数据"]
end
L1 --> L2
subgraph L2["Layer 2 · 快速聚类 (O(n))"]
DHASH["dHash 计算<br/>9×8 → 灰度 → 相邻像素对比"]
LSH["LSH 聚类<br/>8段 × 8位 分桶"]
Gate{"置信度门控"}
Direct["≥ 0.85 → 直接判定为重复"]
Next["0.6 ~ 0.85 → 进入下一层"]
Drop["< 0.6 → 丢弃"]
DHASH --> LSH --> Gate
Gate -->|≥ 0.85| Direct
Gate -->|0.6 ~ 0.85| Next
Gate -->|< 0.6| Drop
end
Next --> L3
Direct --> Emit
subgraph L3["Layer 3 · 防链式误报"]
UF["Union-Find 相似图<br/>路径压缩 + 按秩合并<br/>SIMILARITY_THRESHOLD = 15"]
end
L3 --> L4
subgraph L4["Layer 4 · pHash 边界检查"]
PHASH_Gate{"dHash 距离<br/>5 ~ 8 ?"}
PHASH["pHash DCT 计算<br/>32×32 → DCT → 8×8 中位数"]
PHASH_Gate -->|是| PHASH
PHASH_Gate -->|否| Skip4["跳过"]
end
PHASH --> L5
Skip4 --> L5
subgraph L5["Layer 5 · ORB 精细化匹配"]
G1{"组大小 > 8 ?"}
G2{"dHash 距离 5~8 ?"}
G3{"pHash 距离 10~20 ?"}
ORB["ORB 特征匹配<br/>归一化互相关 (NCC)"]
G1 -->|是| G2
G2 -->|是| G3
G3 -->|是| ORB
G1 -->|否| Skip5
G2 -->|否| Skip5
G3 -->|否| Skip5
end
ORB --> Weight
Skip5 --> Weight
subgraph Weight["加权评分选最优"]
W1["分辨率 35分"]
W2["大小 15分"]
W3["格式 10分"]
W4["来源 25分"]
W5["EXIF 10分"]
W6["时间 5分"]
end
Weight --> Emit["输出: Flow<DuplicateGroup>"]
pie title 保留权重评分体系 (满分 100)
"分辨率" : 35
"来源路径" : 25
"文件大小" : 15
"图片格式" : 10
"EXIF 信息" : 10
"修改时间" : 5
| 维度 | 满分 | 评分依据 |
|---|---|---|
| 分辨率 | 35 | 像素越高分越高(上限 5000 万像素) |
| 来源路径 | 25 | DCIM/Camera=25, Pictures=15, Download=5 |
| 文件大小 | 15 | 越大越好(上限 20MB) |
| 图片格式 | 10 | PNG=10, WebP=7, JPEG=5, GIF=2 |
| EXIF 信息 | 10 | 含 EXIF 为 10 分 |
| 修改时间 | 5 | 近 7 天=5, 近 30 天=3, 近 90 天=1 |
sequenceDiagram
participant User as 用户
participant UI as UI (Compose)
participant VM as ViewModel
participant UC as UseCase
participant DB as Room DB
participant FS as 文件系统
User->>UI: 点击"开始扫描"
UI->>VM: startScan()
VM->>UC: ScanImageUseCase
UC->>DB: 读取已缓存哈希
UC->>FS: MediaStore 扫描图片
FS-->>UC: Flow ImageItem
UC->>DB: 保存图片 + 分桶
UC-->>VM: Flow ScanProgress
VM-->>UI: StateFlow ScanUiState
UI-->>User: 显示扫描进度
Note over UC,VM: 扫描完成
UI->>VM: startDetection
VM->>UC: DetectDuplicateUseCase
UC->>DB: 按分桶查询候选
UC->>UC: 5层流水线处理
UC-->>VM: Flow DuplicateGroup
VM-->>UI: 导航到结果页
UI-->>User: 展示重复组
User->>UI: 选择要删除的图片
UI->>VM: deleteSelected
VM->>UC: DeleteImageUseCase
UC->>FS: 复制到回收站
UC->>DB: 插入回收记录
UC->>FS: 删除原始文件
UC-->>VM: DeleteResult
VM-->>UI: Snackbar(已删除)
Note over UI,User: 5秒倒计时可撤销
graph TD
Start["启动时检测"] --> CPUs{"CPU 核心数 ≥ 8 ?"}
CPUs -->|否| LOW["低端机<br/>并发: 2 线程<br/>算法: 仅 dHash"]
CPUs -->|是| RAM{"内存 ≥ 8GB ?"}
RAM -->|是| HIGH["高端机<br/>并发: 6 线程<br/>算法: dHash + pHash + ORB"]
RAM -->|否| RAM2{"内存 ≥ 4GB ?"}
RAM2 -->|是| MEDIUM["中端机<br/>并发: 4 线程<br/>算法: dHash + pHash"]
RAM2 -->|否| LOW
HIGH --> Temp{"CPU 温度 > 45°C ?"}
Temp -->|是| Reduce["并发数减半"]
Temp -->|否| Normal["正常运行"]
Reduce --> Temp2{"温度 > 50°C ?"}
Temp2 -->|是| Pause["暂停计算, 等待降温"]
- Android Studio Hedgehog (2023.1.1) 或更新版本
- JDK 17+
- Gradle 8.5(wrapper 已内置)
- Android SDK 34
# 克隆项目
git clone <repo-url>
cd PhotoCleaner
# 调试构建
./gradlew assembleDebug
# 离线构建(网络受限环境)
./gradlew assembleDebug --offline
# 发布构建(含 R8 混淆 + 资源压缩)
./gradlew assembleRelease网络受限环境:若无法访问
services.gradle.org,可直接使用本地缓存的 Gradle 8.5 (位于~/.gradle/wrapper/dists/gradle-8.5-bin/),或在 Android Studio 中 设置 File → Settings → Build → Gradle → Offline work。
# 运行所有单元测试
./gradlew test
# 按模块运行
./gradlew :core:common:test # 工具类测试
./gradlew :feature:duplicate:test # 核心算法测试
# 特定测试类
./gradlew :feature:duplicate:test --tests "*HammingDistanceMatcherTest*"./gradlew installDebug构建产物位置:app/build/outputs/apk/debug/app-debug.apk
| 文档 | 说明 |
|---|---|
| 📖 构建与测试指南 | 环境配置、构建命令、测试操作、常见问题 |
| 📐 详细设计方案 | 完整工程级设计文档(V1.5) |
| 维度 | 完成度 | 说明 |
|---|---|---|
| 项目骨架(模块/构建/DI) | ✅ 100% | 8 模块,Hilt 注入,Gradle 8.5 |
| 数据层(Room DB) | ✅ 100% | 5 实体 + 4 DAO + WAL + 索引 |
| 去重算法(5 层流水线) | ✅ 100% | dHash/LSH/UF/pHash/ORB + 置信度门控 + 加权评分 |
| 文件操作(回收站) | ✅ 100% | 删除/恢复/30天过期/WorkManager 自动清理 |
| 应用内升级 | ✅ 100% | 检测/MD5校验/OkHttp下载/FileProvider安装 |
| 设置(DataStore) | ✅ 100% | 可折叠分组,深色/浅色/跟随系统主题 |
| 首页(扫描/检测/结果三合一) | ✅ 100% | HorizontalPager 左右滑动,手动触发去重 |
| 系统适配 | ✅ 100% | 权限分版本/分区存储/前台 Service/FileProvider |
| 性能优化 | ✅ 100% | 设备分级/LSH O(n)/流式 Flow/降级策略 |
| 异常处理 | ✅ 100% | 分级日志/文件轮转/WorkManager 重试/全局崩溃捕获 |
| 隐私合规 | ✅ 100% | 首次启动权限请求 + 隐私政策/服务条款同意 |
| 主链路 | ✅ 100% | 首页→扫描→检测→结果→回收站→设置 完整闭环 |
| 版本 | 功能 | 状态 |
|---|---|---|
| V1.0 MVP | 完全重复检测、相似图片检测、回收站、基础设置、应用内升级 | ✅ 已完成 |
| V1.1 | 相似截图清理、模糊图片检测、微信/QQ 专项扫描、算法精度优化 | 📅 规划中 |
| V1.2 | 大文件清理、截图智能分类、批量导出备份、白名单、扫描历史 | 📅 规划中 |