安全修复: - usesCleartextTraffic=false + network_security_config.xml (LAN例外) - 备份规则排除数据库/SP/缓存 (backup_rules.xml, data_extraction_rules.xml) - ProGuard Release移除 Log.d/Log.v - EmbyRepository 日志脱敏 + API Key安全注释 - EmbyServer URL长度/主机名/端口验证 UI一致性: - LumaDesignSystem 新增16个颜色令牌 + LumaBorders + 2个排版令牌 - 6个屏幕文件硬编码颜色统一到LumaColors - 排版统一 17sp→18sp, 30sp→32sp, 34sp→32sp 功能修复: - 首页MyMediaGrid点击导航到库列表 - 搜索齿轮图标导航到设置 - FavoritesAllScreen/HomeLibraryAllScreen 添加下拉刷新 - RecordsScreen 13+硬编码中文→stringResource() + 修复count bug - 新增16个日历字符串(中英文) 性能优化: - TMDB数据类添加@Immutable注解 - HomeContentCache添加LRU驱逐策略(最多3个快照) - VideoPlayerScreen内部轮询从250ms降至500ms Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
15 KiB
15 KiB
LumaPlayer 全面代码审查报告
项目: LumaPlayer (com.example.myapplication) — Emby 媒体库客户端 审查日期: 2026-09-06 审查范围: UI 一致性 / 安全漏洞 / 性能问题 / 功能缺失 / 代码质量 审查方法: 5 个专项代理并行审查,阅读全部 50+ 源文件
📊 总览
| 审查分类 | 🔴 Critical | 🟠 Major | 🟡 Minor | 合计 |
|---|---|---|---|---|
| 🎨 UI 样式一致性 | 4 | 11 | 15 | 30 |
| 🔒 安全漏洞 | 3 | 7 | 5 | 15 |
| ⚡ 性能问题 | 0 | 7 | 8 | 15 |
| 📋 功能缺失 | 3 | 6 | 14 | 23 |
| 🏗️ 代码质量 | 1 | 7 | 4 | 12 |
| 合计 | 11 | 38 | 46 | 95 |
🚨 Top 10 最关键问题
| # | 严重度 | 分类 | 问题 | 文件 |
|---|---|---|---|---|
| 1 | 🔴 | 安全 | 明文密码存储在 Room 数据库(无加密),设备 Root 后密码可读 | ServerEntity.java:19 |
| 2 | 🔴 | 安全 | usesCleartextTraffic="true" 全局允许 HTTP 明文传输 |
AndroidManifest.xml:11 |
| 3 | 🔴 | 安全 | 认证接口无证书固定,MITM 可截获用户密码 | EmbyRepository.kt:181 |
| 4 | 🔴 | 代码质量 | 全项目零 ViewModel,所有业务逻辑在 Composable 内,配置变更丢失状态 | 全局架构 |
| 5 | 🔴 | UI | 多处硬编码 Color(0xFF152036) 等暗色值,浅色主题下不可见 |
MediaDetailScreen.kt:1842 |
| 6 | 🔴 | 功能 | 播放设置点击无反应(onClick = {}),连播偏好每次重置 |
SettingsScreen.kt:158 |
| 7 | 🔴 | 功能 | 首页"我的媒体"网格点击无反应,空注释 // Handle selection |
HomeScreen.kt:1063 |
| 8 | 🔴 | 功能 | 搜索栏齿轮图标点击无反应 | AppNavHost.kt:214 |
| 9 | 🟠 | 安全 | 备份规则为空,数据库含密码被自动备份到云端 | AndroidManifest.xml:11-12 |
| 10 | 🟠 | 安全 | 50+ 处 catch (_: Exception) 静默吞掉异常,用户看不到真实错误 |
EmbyRepository.kt 多处 |
🎨 一、UI 样式一致性(30 个发现)
🔴 Critical(4 个)
C1. 硬编码暗色背景色破坏浅色模式
- 文件:
MediaDetailScreen.kt:1842, 1929 - 问题:
Color(0xFF152036)作为剧集卡片背景,浅色主题下几乎不可见 - 建议: 替换为
LocalDetailSurface.current或从沉浸式调色板派生
C2. EpisodeListDialog 复制了 GlassDialog 的逻辑
- 文件:
MediaDetailScreen.kt:1730-1750 - 问题: 手动实现
background(pageBottom.copy(alpha = 0.98f))+dialogBorder,与GlassDialog不一致 - 建议: 统一使用
GlassDialog或LumaColors.glassBorder
C3. 在线/离线状态颜色绕过设计令牌
- 文件:
LibraryScreen.kt:119-123,HomeScreen.kt:1260,VideoPlayerScreen.kt:1676 - 问题: 多处直接用
Color(0xFF34C759)/Color(0xFFFF3B30)/Color(0xFFFF453A),三种不同的红色表示"离线"。LumaColors.success/LumaColors.danger已定义但被绕过 - 建议: 统一到
LumaColors.success/LumaColors.danger
C4. LibraryScreen 对话框 overlay 破坏浅色主题
- 文件:
LibraryScreen.kt:934, 1097, 1287 - 问题:
surfaceOverlay = Color.White.copy(alpha = 0.94f)在暗色主题下产生近不透明白色面板 - 建议: 使用
LumaColors.background.copy(alpha = 0.94f)或移除 overlay
🟠 Major(11 个)
| # | 文件 | 问题 | 建议 |
|---|---|---|---|
| M1 | HomeScreen.kt:407 / MediaDetailScreen.kt:938 |
星级评分 Color(0xFFFFC107) 无令牌 |
添加 LumaColors.ratingStar |
| M2 | HomeScreen.kt:837-843 |
RainbowNumber 渐变色未纳入设计系统 |
添加到 LumaColors.rainbowGradient |
| M3 | SettingsScreen.kt:406-416 |
弹窗颜色绕过 LumaColors.regularTint |
统一使用令牌 |
| M4 | MediaDetailScreen.kt:1278-1280 |
"最佳资源"徽章金色无令牌 | 复用 LumaColors.accent |
| M5 | LibraryScreen.kt:844,946,1106 |
图标库渐变色重复 3 次 | 提取为命名常量 |
| M6 | VideoPlayerScreen.kt:1387-1855 |
播放器面板 7 种硬编码颜色 | 创建 PlayerColors 对象 |
| M7 | SearchScreen.kt:730 / RecordsScreen.kt:357 |
海报卡片手动实现而非用 LumaMediaCard |
重构使用统一组件 |
| M8 | SearchScreen.kt:554 / RecordsScreen.kt:259 等 |
多处直接用 AsyncImage 而非 LumaAsyncImage |
统一替换 |
| M9 | 多文件 | 章节标题字体大小不一致(17sp/18sp) | 统一到 LumaTypography.title (18sp) |
| M10 | HomeScreen.kt:650 |
使用 ExtraBold(800) 而设计系统定义 Bold(700) |
统一为 Bold |
| M11 | AppBottomBar.kt:35 |
底栏激活色 #0091FF 与 LumaColors.accent #3B82F6 不同 |
使用 LumaColors.accent |
🟡 Minor(15 个)
| # | 问题 |
|---|---|
| m1 | 间距值 8.dp/10.dp/12.dp 直接写而非用 LumaSpacing.* |
| m2 | LumaSettingRow 使用 14.dp 非标准间距 |
| m3 | LumaSettingRow 标题用 15.sp 字面值而非 LumaTypography.body |
| m4 | 12.sp 字号未纳入排版系统 |
| m5 | LumaPageTopBar 用 20.sp 非标准字号 |
| m6 | Hero 标题用 30.sp + FontWeight.Black 非标准 |
| m7 | MediaDetailScreen 剧集标题 34.sp 非标准 |
| m8-12 | 5 处缺少 contentDescription(轮播指示器、网格卡片、日历箭头、播放器剧集项) |
| m13 | HomeScreen 离线红色 #FF453A 与其他处 #FF3B30 不同 |
| m14 | MediaDetailScreen 返回按钮绕过 LumaIconButton |
| m15 | 边框宽度不一致(0.5dp ~ 1.5dp),无 LumaBorders 令牌 |
🔒 二、安全漏洞(15 个发现)
🔴 Critical(3 个)
S1. 明文密码存储 (CWE-312)
ServerEntity.java:19— 密码和 accessToken 以明文存储在 Room 数据库- 建议: 使用 SQLCipher 加密数据库,或至少用
EncryptedSharedPreferences
S2. 全局允许明文流量 (CWE-319)
AndroidManifest.xml:11—usesCleartextTraffic="true"允许所有 HTTP 明文传输- 建议: 设置为
false,添加network_security_config.xml
S3. 认证无证书固定 (CWE-295/300)
EmbyRepository.kt:181— 密码通过 JSON POST 发送,OkHttp 无CertificatePinner- 建议: 实现证书固定,认证仅走 HTTPS
🟠 Major(7 个)
| # | CWE | 问题 | 文件 |
|---|---|---|---|
| S4 | CWE-312 | 遗留迁移从明文 SP 写入明文 Room | ServerRepository.kt:200 |
| S5 | CWE-798 | TMDB Token 编译进 BuildConfig,APK 反编译可提取 | build.gradle.kts:28 |
| S6 | CWE-598 | api_key 作为 URL 查询参数传输 |
EmbyRepository.kt:869 |
| S7 | CWE-921 | allowBackup="true" + 空备份规则,数据库被云端备份 |
AndroidManifest.xml:11-12 |
| S8 | CWE-319 | buildServerUrl 支持 HTTP 明文协议 |
EmbyServer.kt:142 |
| S9 | CWE-311 | Room 数据库未加密 (emby_player.db) |
AppDatabase.java:152 |
| S10 | 异常处理 | 50+ 处 catch (_: Exception) 静默吞掉错误 |
EmbyRepository.kt 等 |
🟡 Minor(5 个)
| # | 问题 |
|---|---|
| S11 | Log.d("EmbyAuth", ...) 在 Release 中仍可见 |
| S12 | 服务器 URL 输入无长度/格式验证 |
| S13 | IconLibrary.kt:167 用 java.net.URL 而非共享 OkHttp 客户端 |
| S14 | Log.e 输出异常堆栈可能泄露服务器细节 |
| S15 | ProGuard 未配置 -assumenosideeffects 移除 Log.d/v |
⚡ 三、性能问题(15 个发现)
🟠 Major(7 个)
| # | 问题 | 文件 | 影响 |
|---|---|---|---|
| P1 | TmdbCastMember / TmdbRecommendation 等缺少 @Immutable |
TmdbRepository.kt:21-59 |
不必要的重组 |
| P2 | FavoriteEntity 是 Java 可变类传入 Compose,永远不稳定 |
FavoriteEntity.java:13 |
LazyGrid 每项重组 |
| P3 | MediaDetailScreen 用 Column + verticalScroll 而非 LazyColumn |
MediaDetailScreen.kt:489 |
所有子组件同时组合,内存高 |
| P4 | 播放器 250ms 轮询 + 内部重复轮询,4次/秒重组整个控件 | VideoPlayerScreen.kt:389-434 |
UI 持续重组 |
| P5 | FavoritePosterCard 在主线程解析 JSON |
RecordsScreen.kt:363 |
滚动时卡顿 |
| P6 | HomeContentCache 无限增长无驱逐 |
HomeScreen.kt:136 |
内存泄漏风险 |
| P7 | VideoPlayerScreen 单函数 ~1600 行,~40 个状态变量 |
VideoPlayerScreen.kt:260 |
任何状态变更重组整个播放器 |
🟡 Minor(8 个)
| # | 问题 |
|---|---|
| P8 | MyMediaGrid LazyRow 缺少 key |
| P9 | SearchResultsCache 每次服务器完成都 .toList() 拷贝 |
| P10 | castMembers 每次重组做 O(n×m) 名称匹配 |
| P11 | FavoriteSectionHeader 显示 "$count/$count" 无意义 |
| P12 | 播放器剧集 LazyColumn 缺少 key |
| P13 | CalendarEpisodeCard 每次重组调 LocalDate.now() |
| P14 | 搜索并行化可改进(季/集可缓存) |
| P15 | Hero shuffled() 在主线程执行 |
📋 四、功能缺失(23 个发现)
🔴 Critical(3 个)
F1. 播放设置为空壳
SettingsScreen.kt:158— 点击无反应,无播放速度/连播/字幕偏好等设置- 连播偏好 (
confirmBeforeContinuousPlayback) 每次打开播放器重置
F2. 首页"我的媒体"网格点击无反应
HomeScreen.kt:1063—// Handle selection空注释,点击无导航
F3. 搜索栏齿轮图标无功能
AppNavHost.kt:214—onGearClick = {}空实现
🟠 Major(6 个)
| # | 问题 | 文件 |
|---|---|---|
| F4 | HomeLibraryAllScreen 无下拉刷新 |
HomeLibraryAllScreen.kt:149 |
| F5 | RecordsScreen 收藏/日历 Tab 无下拉刷新 |
RecordsScreen.kt:137 |
| F6 | FavoritesAllScreen 无下拉刷新 |
FavoritesAllScreen.kt:59 |
| F7 | SearchAllScreen 无分页/无限滚动 |
SearchAllScreen.kt:59 |
| F8 | FavoriteSectionHeader 显示 "$count/$count" 数字重复 |
RecordsScreen.kt:321 |
| F9 | 日历 Tab 13+ 个中文字符串硬编码,英文模式不翻译 | RecordsScreen.kt:580-899 |
🟡 Minor(14 个)
| # | 问题 |
|---|---|
| F10 | HomeLibraryAllScreen 滚动加载无底部 loading 指示器 |
| F11 | 搜索历史清除无确认对话框 |
| F12 | 播放速度偏好不持久化 |
| F13 | 无 Deep Link 支持 |
| F14 | 收藏 Tab 无 loading 状态 |
| F15 | 详情页加载失败无重试按钮 |
| F16 | 所有导航转场相同,详情→播放器无特殊过渡 |
| F17 | 播放器错误无重试机制 |
| F18 | 空库分区显示标题但无内容提示 |
| F19 | 缓存管理仅清除图标缓存,不含 Coil/搜索历史 |
| F20 | 日历依赖 TMDB 无回退,失败静默空白 |
| F21 | 排序弹窗固定高度 304.dp |
| F22-23 | 多处交互元素缺少 contentDescription |
🏗️ 五、代码质量(12 个发现)
🔴 Critical(1 个)
Q1. 零 ViewModel 架构 — 全项目无 ViewModel
- 所有业务逻辑在
@Composable函数中 MainApp.kt:146-206中addServer/updateServer含认证逻辑AppNavHost.kt:397-519中 PlayerDestination 有 8 个remember状态变量- 影响: 配置变更(旋转/深色切换)丢失状态,无法单元测试
- 建议: 至少引入
HomeViewModel,MediaDetailViewModel,PlayerViewModel
🟠 Major(7 个)
| # | 问题 | 文件 |
|---|---|---|
| Q2 | 所有 Repository 是 object 单例,无 DI 框架 |
全局 |
| Q3 | 50+ 处 catch (_: Exception) 静默吞掉异常 |
EmbyRepository.kt 等 |
| Q4 | addServer / updateServer 80% 代码重复 |
MainApp.kt:152-206 |
| Q5 | 5 个文件超 1000 行(最大 LibraryScreen 2031 行) | 见上方 |
| Q6 | 无结构化协程作用域,单例 CoroutineScope 永不取消 | EmbyRepository.kt:151 |
| Q7 | 仅 9 个测试文件覆盖 ~18K 行代码,Repository 层零测试 | test/ 目录 |
| Q8 | playbackReportState MutableMap 无同步文档 |
EmbyRepository.kt:150 |
🟡 Minor(4 个)
| # | 问题 |
|---|---|
| Q9 | 包名 com.example.myapplication 是模板默认值 |
| Q10 | EmbyAuthRequest 字段用 PascalCase 违反 Kotlin 命名 |
| Q11 | LumaMotion 持续时间命名缺描述性后缀 |
| Q12 | ExperimentalAnimationApi opt-in 可能不再需要 |
🛣️ 优化路线图
📅 短期(1 周)— 安全修复 + 关键 Bug
- 安全:
usesCleartextTraffic="false"+network_security_config.xml - 安全: 配置备份规则排除数据库
- 安全: 添加 ProGuard 规则移除
Log.d/v - 安全: API Key 改用 HTTP Header 传输
- 功能: 修复
MyMediaGrid点击无反应 → 导航到库列表 - 功能: 修复搜索齿轮图标 → 导航到设置或移除
- 功能: 修复
"$count/$count"显示 bug - UI: 统一在线/离线颜色到
LumaColors令牌
📅 中期(1 个月)— 架构重构 + 安全加固
- 安全: 实现数据库加密(SQLCipher 或 EncryptedSharedPreferences)
- 安全: 添加 OkHttp 证书固定
- 架构: 引入至少 3 个 ViewModel(Detail/Player/Home)
- 架构: 引入 Hilt 依赖注入
- 性能: MediaDetailScreen 改用 LazyColumn
- 性能: VideoPlayerScreen 拆分子组件减少重组范围
- 功能: 实现播放设置页面(速度/连播/字幕偏好)
- 功能: 添加 3 个页面的下拉刷新
- 国际化: 日历 Tab 中文硬编码迁移到 strings.xml
📅 长期(3 个月)— 完善 + 可维护性
- 架构: 拆分 5 个大文件(LibraryScreen/MediaDetailScreen/VideoPlayerScreen/HomeScreen/EmbyRepository)
- 安全: TMDB Token 移至后端代理
- 测试: Repository 层单元测试覆盖
- 测试: Compose UI 测试覆盖关键流程
- UI: 统一排版系统(消除 17sp/20sp/30sp/34sp 离群值)
- UI: 创建
LumaBorders设计令牌 - 功能: Deep Link 支持
- 功能: 完善缓存管理(Coil/搜索历史/视频缓存)
📈 综合健康评分
评分:6.5 / 10
| 维度 | 评分 | 说明 |
|---|---|---|
| 🎨 UI 一致性 | 6/10 | 设计系统完善但被大量绕过,硬编码颜色/字号泛滥 |
| 🔒 安全性 | 4/10 | 密码明文存储 + 明文传输 + 无证书固定,多项严重漏洞 |
| ⚡ 性能 | 6/10 | 已做三轮优化但核心架构问题(无 LazyColumn、大重组范围)仍在 |
| 📋 功能完整度 | 7/10 | 核心功能齐全,3 个空壳占位需实现 |
| 🏗️ 代码质量 | 5/10 | 无 ViewModel、无 DI、静默异常、大文件、测试不足 |
优势
- ✅ 自有 LumaDesignSystem 设计令牌体系
- ✅ LiquidGlass 创新 UI 效果
- ✅ 已完成三轮系统性性能优化(@Immutable、remember、并行化)
- ✅ 分层架构清晰(data/screens/navigation/ui)
- ✅ 双语支持框架完善
- ✅ Room + Migration 管理规范
核心短板
- ❌ 安全基础薄弱(明文密码 + 明文传输)
- ❌ 缺少 ViewModel 导致状态管理脆弱
- ❌ 设计令牌定义完善但被大量绕过
- ❌ 静默异常处理掩盖真实问题
报告由 5 个专项审查模块并行审查后汇总生成。审查时间:2026-09-06。