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

9.2 KiB
Raw Blame History

Hearth 启动器 · 开发日志

项目:HearthAndroid 启动器)· 包名 top.yeij.hearth 日期:2026-08-16

1. 项目概述

面向瑞芯微 RK3566 横屏开发板(立创·泰山派)的 Android 启动器(Home)。 核心架构:界面全 H5 渲染,原生只当「能力层」,通过 JS Bridgewindow.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-blursaturation 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.postmarshal 到主线程。
  3. 跨线程竞态(最终 review 发现):WebAppContainer 的 open/close/switchTo 在主线程写、listTabs 在桥线程读同一 mutableListOf。解决:状态方法全部 @Synchronized
  4. innerHTML XSSapplist/webapplist/topbar 用 innerHTML 拼用户数据,桌面 WebView 持有桥权限可被注入。解决:全部改 createElement + textContent/dataset
  5. 首页空卡片fetchCards 在仓库未接线时返回 "[]"layout([]) 渲染空。解决:home.js 兜底补内置 time 卡。
  6. 媒体卡权限:见 §3NotificationListenerService 方案。

6. 遗留项(deferred,不影响 merge,后续处理)

  • 真机验证:无 adb 设备,壁纸/柔光玻璃/媒体卡/多标签导航均未真机冒烟。
  • drawableToBase64 未 recycle bitmaptimeTicker 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 连宿主机 Termuxaska@127.0.0.1:2022),KernelSU rootlogcat 抓日志排查。
  • 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 未授权「通知使用权」时 addOnActiveSessionsChangedListenerSecurityException: 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-boxpadding 吃掉 height 导致 bar 高度 0 的坑;拖动 seek 调 transportControls.seekTo
  • 多会话滑动:左右横划切换(方向过渡动画),多个媒体通知(音乐/听书/视频)并存。

8.4 玻璃效果与壁纸

  • 壁纸进 H5backdrop-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)。
  • 切页隐藏 webapphideWebApps()(内容 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/卡片目录默认占位地址,真机部署时配置真实服务器。