Files
Hearth/docs/devlog-2026-08-16.md

123 lines
9.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Hearth 启动器 · 开发日志
> 项目:HearthAndroid 启动器)· 包名 `top.yeij.hearth`
> 日期:2026-08-16
## 1. 项目概述
面向瑞芯微 RK3566 横屏开发板(立创·泰山派)的 Android 启动器(Home)。
核心架构:**界面全 H5 渲染,原生只当「能力层」**,通过 JS Bridge`window.HearthBridge`)双向调用,桌面 UI、卡片、webAPP 均可远程下发、动态扩展,无需重装 APK。
目标平台:Android 11API 30)· 横屏 800×480 · 高 DPI(自适应系统 density)· `minSdk=30` 不做低版本兼容。
## 2. 环境搭建(arm64 工具链,本次最大障碍)
本机是 aarch64 容器,而 Android SDK 的构建工具(aapt2 等)官方只发布 x86_64 Linux 版。搭建过程:
| 组件 | 方案 | 来源 |
|---|---|---|
| JDK 17 | apt | 清华源 |
| Gradle 8.9 | 二进制包 | 腾讯镜像 |
| Android SDK platform-35 + build-tools 34.0.0 | 手动下载 | 腾讯镜像(dl.google.com 不通,且镜像无 android-34 正式版,故 `compileSdk=35` |
| **arm64 aapt2/zipalign/split-select** | drop-in 替换 | Commit451/android-arm-build-toolsgh-proxy.com 加速) |
| 依赖仓库 | 阿里云 google/central 镜像 | `maven.aliyun.com` |
**关键结论**:官方 Google Maven 无 arm64 Linux 版 aapt2Issue #227219818),只能社区构建。通过 `android.aapt2FromMavenOverride` 指向 arm64 aapt2 绕开 Google Maven 的 x86_64 下载。
另:AGP 首次构建会卡死在 `dl.google.com` 的 SDK 自动下载(SYN_SENT 挂起),需 `android.builder.sdkDownload=false` 关闭。
## 3. 设计决策
- **全 H5 渲染**:桌面 = 全屏 WebView + H5,原生只提供查应用/启动/媒体状态/下载缓存等能力。
- **UI 风格 Miuix / HyperOS 4 视觉**(纯 CSS 还原,非 Compose 库):
- **壁纸透明**Activity `showWallpaper` + WebView `setBackgroundColor(TRANSPARENT)` + H5 `html/body` 透明。
- **柔光玻璃**(HyperOS 4 设计):卡片用 `backdrop-filter: blur(20px) saturate(1.2) brightness(1.05) contrast(1.1)` + 半透明背景 + 1px 描边(参数参考 Miuix `miuix-blur`saturation 1.2 / brightness +0.05 / contrast 1.1)。
- **卡片插件化**:每个卡片 = 独立 HTML + manifest(声明数据源/权限),可远程下发。
- **webAPP 多标签**:每个标签 = 一个独立 WebView(`Map<String, WebView>`),内容 WebView topMargin 44dp 露出 H5 顶栏。
- **媒体卡权限方案**`MEDIA_CONTENT_CONTROL` 是 signature|privileged 权限普通 APK 拿不到,改用 **NotificationListenerService**(用户授权"通知使用权"),`getActiveSessions(ComponentName)` 传 listener 组件作为授权凭据。
- **编译环境适配**`compileSdk=35` + AGP `8.6.1`(匹配 arm64 aapt2 8.6.x)。
## 4. 实现过程(14 个任务,TDD + 逐任务 review
| 阶段 | 任务 |
|---|---|
| 原生骨架 | 项目骨架+窗口管理 → WebViewManager+JsBridge → AppRepository |
| 桌面+列表 | H5 桌面壳 → 安卓APP列表 → CacheManager+WebApp清单 → H5应用列表 |
| 多标签 | WebAppContainer(标签状态数据层)→ 顶栏 H5 + tabs 纯函数 |
| 卡片系统 | CardRepository+首页三栏 → MediaSessionSource+媒体卡 |
| 收尾 | 设置页 → 集成收尾(接线+NotificationListenerService)→ 补充 webAPP 多标签 JS Bridge |
测试:Kotlin 单测 32 个(AppRepository/JsBridge/WebAppRepository/WebAppContainer/CacheManager 等)+ H5 node:test 8 个(cards.js 布局降级 / tabs.js 标签状态),全绿。
## 5. 关键问题与解决
1. **aapt2 arm64**:官方无 → Commit451 社区构建 drop-in 替换(见 §2)。
2. **JS 桥线程 vs 主线程**CriticalTask 14 review 发现):`@JavascriptInterface` 方法在 JavaBridge 后台线程运行,直接操作 View 会抛 `CalledFromWrongThreadException`。解决:所有 View 操作 `postToMainThread`(注入 `desktopWebView.post`marshal 到主线程。
3. **跨线程竞态**(最终 review 发现):WebAppContainer 的 `open/close/switchTo` 在主线程写、`listTabs` 在桥线程读同一 `mutableListOf`。解决:状态方法全部 `@Synchronized`
4. **innerHTML XSS**applist/webapplist/topbar 用 innerHTML 拼用户数据,桌面 WebView 持有桥权限可被注入。解决:全部改 `createElement` + `textContent`/`dataset`
5. **首页空卡片**`fetchCards` 在仓库未接线时返回 `"[]"``layout([])` 渲染空。解决:home.js 兜底补内置 time 卡。
6. **媒体卡权限**:见 §3NotificationListenerService 方案。
## 6. 遗留项(deferred,不影响 merge,后续处理)
- 真机验证:无 adb 设备,壁纸/柔光玻璃/媒体卡/多标签导航均未真机冒烟。
- `drawableToBase64` 未 recycle bitmap`timeTicker` setInterval 未清理。
- 沉浸模式仅在 onCreate 设置,返回前台未重设(建议 onResume)。
- 内容 WebView 无 onPause/onResume 生命周期同步。
- 设置页 Switch/Slider/版本号静态,交互未接。
- webAPP 服务器地址、卡片目录地址为占位常量,需真机配置。
- 二期:沉浸首页 freeform 小窗(需固件 `enable_freeform_support`)。
## 7. 分支与提交
开发走 `dev` 分支(`main` 为发布分支)。设计文档 `docs/superpowers/specs/`、实现计划 `docs/superpowers/plans/`、本日志 `docs/`
## 8. 真机调试与 UI 迭代(宿主机 OnePlus PLK110
### 8.1 真机环境
- 通过 SSH 连宿主机 Termux`aska@127.0.0.1:2022`),KernelSU root`logcat` 抓日志排查。
- APK 用容器内 Python `http.server` 分发(`http://localhost:8080/Hearth-debug.apk`)。
- 真机是 Android 16API 36)手机,非目标板(RK3566/Android 11),但能验证 H5 逻辑与大部分原生行为。
### 8.2 关键崩溃修复
1. **黑屏(实为崩溃)**`enterImmersive()``setContentView()` 之前调 `window.insetsController`,此时 DecorView 未创建(null)→ NPE。日志堆栈 `Attempt to invoke WindowInsetsController on a null object reference`。修复:把 `enterImmersive()` 移到 `setupWebView()`(内含 setContentView)之后。
2. **闪退**`MediaSessionSource` 未授权「通知使用权」时 `addOnActiveSessionsChangedListener``SecurityException: Missing permission to control media`。修复:`start()` 内 try-catch 降级 + 设置页加授权引导项(跳 `ACTION_NOTIFICATION_LISTENER_SETTINGS`)。
### 8.3 媒体卡完善
- **封面**`METADATA_KEY_ART` → 128px JPEG base64;加**封面缓存**(切歌瞬间 ART 短暂为 null,缓存兜底避免横划后封面消失)。
- **控制按钮**:上一首/暂停/下一首(`transportControls`),**按索引**作用于当前横划到的会话。
- **实时歌词**:网易云歌词实时更新在**通知标题**(不在 MediaSession metadata),`NotificationListenerService` 监听 `CATEGORY_TRANSPORT` 通知的 `EXTRA_TITLE`,标题优先用通知实时歌词。
- **进度条 + 时长 + 拖动 seek**:轮询兜底(registerCallback 在歌词场景不触发);进度条 `box-sizing:border-box``padding` 吃掉 `height` 导致 bar 高度 0 的坑;拖动 seek 调 `transportControls.seekTo`
- **多会话滑动**:左右横划切换(方向过渡动画),多个媒体通知(音乐/听书/视频)并存。
### 8.4 玻璃效果与壁纸
- **壁纸进 H5**`backdrop-filter` 只能模糊 WebView 内部内容,而壁纸原来在 WebView 之外(透明透出),故无模糊。原生 `getWallpaper()` 把壁纸转 base64 作 body 背景,卡片才能真正模糊壁纸。
- **动态壁纸降级**:检测 `wallpaperInfo != null` 返回空,H5 保持透明透出动态壁纸,玻璃模糊降级为普通半透明。
- **玻璃开关**:设置页开关,默认关(普通半透明),开 = 液态玻璃(磨砂模糊 + 边缘高光)。
### 8.5 设置页交互
主题三态(跟随/浅色/深色,`data-theme` 覆盖 `prefers-color-scheme`)、背景遮罩(夜间,开关+明暗度)、系统亮度(`WRITE_SETTINGS` 授权)、webAPP 服务器地址(SharedPreferences)、检查更新(重拉清单);滑块统一 touch 拖动。
### 8.6 webapp 多标签
- **原生标签面板**:H5 面板是桌面 WebView 底层元素,被上层内容 WebView 遮住(Android View 层级无法用 z-index 跨越)。改 **PopupWindow 原生面板**覆盖显示,内容 WebView 只预留顶栏高度(不压 webapp)。
- **切页隐藏 webapp**`hideWebApps()`(内容 WebView GONE,标签保留)+ `body.webapp-active` 隐藏列表内容,切回自动恢复。
- **内容 WebView** 透明背景(未声明背景色的 webapp 透出壁纸)。
### 8.7 性能
- **页面预加载**:启动后后台并行拉取 APP 列表/webAPP 清单/卡片目录缓存,切页直接渲染。
- **媒体卡增量更新**:歌词实时变化时只更新媒体卡文字,不重建整个首页(之前每次歌词变化都重建导致卡顿)。
## 9. 仍在遗留
- 真机为手机(Android 16),目标板 RK3566/Android 11 尚未验证(壁纸/玻璃/沉浸式/媒体卡的板子适配)。
- 沉浸模式未在 onResume 重设;内容 WebView 无 onPause/onResume 同步。
- 二期:沉浸首页 freeform 小窗(需固件 `enable_freeform_support`)。
- webAPP/卡片目录默认占位地址,真机部署时配置真实服务器。