Files
Hearth/docs/superpowers/specs/2026-08-16-hearth-launcher-design.md
T

13 KiB
Raw Blame History

Hearth 启动器 · 设计文档

版本:v1.0 日期:2026-08-16 包名:top.yeij.hearth

1. 概述

Hearth 是一个面向瑞芯微 RK3566 横屏开发板(立创·泰山派)的 Android 启动器(Home)。 核心特色:界面全 H5 渲染,原生只当「能力层」,通过 JS Bridge 向 H5 暴露系统能力, 使得桌面 UI、卡片、webAPP 均可远程下发、动态扩展,无需重装 APK。

目标平台

硬件 立创·泰山派 RK3566 开发板
系统 Android 11API 30
屏幕 横屏 800×480 物理像素,高 DPI(≈300+,自适应系统 density
兼容策略 minSdk = 30,仅针对该板,不做低版本兼容

2. 需求汇总(已确认)

  1. 全 H5 渲染架构:桌面 = 全屏 WebView 承载 H5,原生只提供系统能力(查应用/启动应用/媒体状态/下载缓存等)。
  2. 全局左侧边栏Miuix NavigationRail 形态,可切换(收起 80px 纯图标 / 展开 240px 图标+文字), 用于切换 5 个页面:首页 / 沉浸首页 / H5 应用列表 / 安卓 APP 列表 / 设置。
  3. 首页:左中右三栏卡片流。大字时间卡始终保留;媒体卡动态(有活动媒体才显示); 日历/天气/小工具填充富余空间。首页无应用入口(不做 Dock),应用只从列表页打开
  4. 卡片插件化:每个卡片 = 独立 HTML + 一份 manifest(声明所需数据源/权限),可后期扩展、可远程下发。
  5. 沉浸首页(二期):左 2/3 freeform 窗口跑第三方 App + 右侧卡片列(时间/媒体/天气)。
  6. 列表页:安卓 APP 列表 = 图标网格 + 搜索;H5 应用列表 = webAPP 富卡片(缩略图 + 在线/离线标签)。
  7. webAPP 下发:JSON 清单 + 可选离线包,远程下发 + 本地缓存。
  8. UI 风格Miuix / HyperOS 视觉(纯视觉,H5 用 CSS 还原,非 Compose 库)。
  9. 状态栏:系统默认沉浸式(全屏隐藏,顶部下滑一次展开,几秒自动收起)。
  10. 夜间模式:跟随系统(prefers-color-scheme)。
  11. 横屏锁定:固定横屏。

3. 架构

┌──────────────────────── Hearth APK ────────────────────────┐
│  MainActivity(唯一 Activity                              │
│    ├─ 窗口管理:全屏沉浸式 / 横屏锁定 / 夜间模式             │
│    └─ WebView(全屏,加载 assets/index.html                │
│           │  window.HearthBridge (JS Bridge)                 │
│           ▼                                                 │
│  ┌────────────────────────────────────────────────────┐    │
│  │ 能力层(原生 Kotlin)                                 │    │
│  │  ├─ AppRepository      查应用列表/图标/启动          │    │
│  │  ├─ WebAppRepository   拉 webAPP 清单/离线包         │    │
│  │  ├─ CardRepository     卡片清单拉取/缓存             │    │
│  │  ├─ MediaSessionSource 媒体状态监听                   │    │
│  │  ├─ CacheManager       文件缓存(清单/离线包/图标)   │    │
│  │  └─ JsBridge           暴露给 H5 的方法               │    │
│  └────────────────────────────────────────────────────┘    │
└────────────────────────────────────────────────────────────┘
         │ 网络(webAPP / 卡片 服务器)
         ▼
   ┌─ 服务器 ──────────────┐
   │  manifest.json        │ ← webAPP 清单
   │  offlines/*.zip       │ ← 可选离线包
   │  cards/catalog.json   │ ← 卡片目录
   │  cards/<id>/card.html │ ← 卡片资源
   └───────────────────────┘

模块职责(单一职责)

模块 职责 依赖
MainActivity 窗口、生命周期、横屏锁、沉浸式 WebViewManager
WebViewManager WebView 配置(内核设置、viewport、注册 JS Bridge JsBridge
JsBridge 所有 @JavascriptInterface 方法,H5 唯一入口 各 Repository/Source
AppRepository PackageManager 封装:应用列表、base64 图标、启动应用
WebAppRepository 拉取 webAPP 清单、下载离线包、解析 CacheManager
CardRepository 拉取卡片目录、下载卡片资源 CacheManager
MediaSessionSource 监听活跃媒体会话,推送媒体元数据
CacheManager 文件读写缓存(清单/离线包/卡片/图标)

数据流(核心路径)

  1. 启动MainActivity 全屏 → WebView 加载 assets/index.html(H5 桌面,随 APK 打包,保证离线可用)。
  2. 渲染 APP 列表H5 调 listApps() → 原生查 PackageManager → 回调 JSON(含 base64 图标)→ H5 渲染网格。
  3. 渲染 webAPP 列表H5 调 fetchWebApps() → 原生拉服务器清单 → 缓存 → 回调 → H5 渲染富卡片。
  4. 启动 APP:点击 → H5 调 launchApp(pkg) → 原生 startActivity
  5. 打开 webAPP:点击 → H5 调 openUrl(url) → WebView 加载;返回桌面 = 重新 load 本地 index.html。
  6. 首页卡片H5 调 fetchCards() → 原生拉卡片目录 → 下载卡片 HTML + 数据 → H5 渲染三栏卡片流。
  7. 媒体卡:原生 MediaSessionSource 监听媒体变化 → 事件推给 H5 → H5 显示/隐藏媒体卡。

4. JS Bridge 协议

H5 通过 window.HearthBridge 访问原生能力。所有方法异步回调(callbackId 模式或 Promise 封装)。

4.1 H5 → 原生(方法调用)

方法 参数 返回 说明
listApps() AppInfo[] 已装应用列表(包名/名称/base64 图标)
launchApp(pkg) 包名 void 启动第三方应用
fetchWebApps() WebApp[] 拉取/返回 webAPP 清单(含缓存逻辑)
openUrl(url) URL void WebView 加载 URL
backToHome() void 重新加载本地 index.html
fetchCards() Card[] 拉取/返回卡片目录
getDeviceInfo() DeviceInfo 分辨率、density、深色模式等
launchInBounds(pkg, x,y,w,h) 二期 void freeform 启动到指定区域

4.2 原生 → H5(事件推送)

原生通过 HearthEventsJS 注入回调)推送事件:

事件 载荷 触发时机
media-session-changed MediaInfo 媒体播放状态/歌曲变化
theme-changed "light"|"dark" 系统夜间模式切换
webapp-updated WebApp[] webAPP 清单更新

4.3 数据结构

// AppInfo
{ "packageName": "com.x.y", "label": "音乐", "icon": "data:image/png;base64,..." }

// WebApp
{
  "id": "cloud-music", "name": "云音乐",
  "icon": "https://host/icon.svg", "url": "https://host/app/index.html",
  "offline": { "package": "offline/cloud-music.zip", "version": "1.0.0" }
}

// MediaInfo
{ "title": "海阔天空", "artist": "Beyond", "album": "乐与怒",
  "cover": "data:image/png;base64,...", "position": 120000, "duration": 245000,
  "playing": true, "packageName": "com.x.player" }

// DeviceInfo
{ "widthPx": 800, "heightPx": 480, "density": 2.0, "darkMode": true }

5. 卡片插件系统

5.1 卡片包结构

cards/<card-id>/
├── card.html        # 卡片 UI(独立 HTML 片段)
├── manifest.json    # 声明数据源/权限
└── assets/          # 卡片私有资源(可选)

5.2 manifest 格式

{
  "id": "media-card",
  "name": "媒体卡片",
  "version": "1.0.0",
  "entry": "card.html",
  "dataSources": ["time", "media-session"],   // 需要的数据源
  "permissions": ["MEDIA_CONTENT_CONTROL"],     // 对应的系统权限
  "priority": 1,                                // 优先级(数字小者优先占位)
  "size": { "min": "1x1", "max": "2x2" }        // 尺寸偏好
}

5.3 数据源类型

原生能力层提供的「数据源」,卡片通过 manifest 声明所需:

数据源 说明 权限
time 时间/日期
media-session 媒体状态 MEDIA_CONTENT_CONTROL + 通知监听授权
weather 天气 网络 + 城市配置
calendar 日历 READ_CALENDAR
system 系统信息(内存/存储) 无/部分

5.4 卡片加载与降级

  1. H5 获取卡片目录 → 下载卡片资源到本地缓存。
  2. priority 排序,三栏容器依次放置。
  3. 卡片数据源未授权/无数据时,该卡片不渲染,由低优先级卡片(日历/天气)填充。
  4. 大字时间卡为内置卡片,始终保留,不参与降级。

6. webAPP 下发协议

6.1 清单格式(manifest.json

{
  "version": 1,
  "updatedAt": 1723785600,
  "apps": [
    {
      "id": "cloud-music", "name": "云音乐",
      "icon": "https://host/icon.svg", "url": "https://host/app/index.html",
      "offline": { "package": "offline/cloud-music.zip", "version": "1.0.0", "size": 1048576 }
    }
  ]
}

6.2 下发与缓存策略

  1. 启动/定时拉取清单 → 对比本地 version
  2. 有新版本 → 下载清单 + 各 webAPP 图标/离线包。
  3. 本地缓存失败降级:有旧清单用旧清单,无清单显示空态 + 错误提示
  4. webAPP 打开时:有离线包且离线 → 加载本地解压目录;否则加载远程 URL。

7. UI 设计

7.1 视觉规范(Miuix / HyperOS 风格,CSS 还原)

  • 配色:双色 token(CSS 变量),浅色/深色两套;深色模式背景纯黑 #000,卡片 #151518
  • 圆角squircle 平滑圆角(卡片 14px 左右)。
  • 字体MiSans(远程字体或系统默认)。
  • 强调色:小米橙 #ff6900(选中态/进度条)。
  • 侧边栏选中态:橙色高亮药丸。

7.2 页面布局

页面 布局
首页 三栏卡片流:大字时间(始终)+ 媒体卡(动态)+ 日历/天气/小工具(填充)
沉浸首页(二期) 左 2/3 freeform 窗口 + 右卡片列(时间/媒体/天气)
安卓 APP 列表 顶部搜索框 + 图标网格(图标+名称)
H5 应用列表 顶部搜索框 + webAPP 富卡片(缩略图 + 在线/离线标签)
设置 Miuix Preference 分组:显示 / 网络 / 关于

7.3 侧边栏

  • 收起:80px,纯图标(无文字)。
  • 展开:240px,图标 + 文字横排,选中项后橙色高亮药丸。
  • 左上角按钮切换收起/展开。

7.4 夜间模式

  • H5 用 CSS 变量 + prefers-color-scheme 跟随系统。
  • 原生保证 WebView 跟随 Configuration.uiMode,无需额外处理。

8. 窗口管理

  • 全屏沉浸式WindowInsetsController.hide(statusBars() | navigationBars()),行为 BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE(系统默认:下滑一次展开、几秒自动收起)。
  • 横屏锁定screenOrientation = landscape
  • 默认桌面Manifest 声明 HOME + DEFAULT intent-filter,用户手动设为默认。

9. 权限模型

权限 用途 授予方式
QUERY_ALL_PACKAGES 查询已装应用列表 launcher 豁免,声明即生效
MEDIA_CONTENT_CONTROL 读取媒体状态 运行时 + 通知监听授权(用户手动)
READ_CALENDAR 日历卡片数据 运行时授权
INTERNET 拉取 webAPP/卡片 声明即生效

10. 错误处理

场景 处理
webAPP 清单拉取失败 用本地缓存清单;无缓存则空态 + 提示
离线包下载失败 标记「在线」,下次拉取重试
媒体权限未授权 媒体卡不渲染,降级日历/天气
WebView 加载失败 错误页 + 重试按钮
系统 WebView 缺失 启动检测,提示(或引导安装)
第三方 App 无法 freeform(二期) 捕获异常,提示该 App 不支持小窗

11. 测试策略

  • TDD:先写测试再写实现。
  • 原生单测Repository 层(mock PackageManager / 网络 / CacheManager)。
  • JS Bridge 协议测试:mock H5 调用,验证方法签名与返回结构。
  • H5 侧:卡片/页面为纯静态,用轻量 JS 单测覆盖数据拼接与降级逻辑。
  • 真机集成:adb 安装到板子,验证 HOME、沉浸式、媒体权限、webAPP 拉取。

12. 技术栈与依赖

选择
语言 Kotlin
SDK minSdk 30 / targetSdk 30
WebView 系统内核(需上板验证存在)
网络 OkHttp
JSON Gson
H5 纯 HTML/CSS/JS,无框架(SPA 手写)
构建 GradleAndroid Gradle Plugin

13. 二期规划(不在本期实现)

  1. freeform 小窗(沉浸首页):FreeformWindowManager 模块 + launchInBounds 能力。
    • 前提:固件开启 enable_freeform_support,需上板验证。
  2. 更多首页候选卡片(随卡片插件系统扩展)。

14. 明确不做(YAGNI

  • kiosk 防退出 / 屏蔽物理按键
  • 屏幕常亮管理
  • 多用户 / Widget 宿主
  • 应用图标拖拽 / 文件夹 / Dock