把 Home Assistant 里某一个实体的状态变化,做成 Android 手机上的实时流水列表。
为 Android 6.0 老设备设计:不经过 WebView、不依赖 Google 服务、依赖为零、APK 只有 36.7 KB。
┌────────────────────────────────────┐
│ sensor.my_text [设置] │
│ ⚡ 实时 · 共 128 条变化 │
├────────────────────────────────────┤
│ 09-05 14:23:05 │
│ 25.3 │
│ 09-05 14:22:41 │
│ 25.1 │
│ 09-05 14:20:12 │
│ 24.9 │
└────────────────────────────────────┘
手上有一台还能用但已经过时的 Android 6.0 手机,想拿它当 HA 的实体状态监视屏,结果发现三条路都堵死了:
| 方案 | 为什么不行 |
|---|---|
| 官方 HA App | 界面由 WebView 渲染。Android 6 的 Chrome / System WebView 最高只能到 106(Google 已停止为 6.0 发版),加载 Lovelace 卡顿甚至白屏 |
| 浏览器打开 HA 网页版 | 同上,而且这台机器的浏览器连普通现代网页都会显示异常 |
| 自己做个网页 | 还是要把命运交给那个烂 WebView |
绕开 WebView 就全通了。 原生控件(ListView / EditText)不需要任何浏览器引擎,于是有了这个 App。
同时刻意做到了零第三方依赖:不用 androidx、不用 OkHttp、不用 Gson、不用 GMS,连 WebSocket 客户端都是自己实现的。代价是代码多一点,好处是 APK 极小、在弱机上启动快、也不受 AndroidX 版本策略影响。
- WebSocket 实时推送,延迟约 50~150 毫秒(局域网)(此功能尚未经过测试,不保证可用性)
- 自动重连:指数退避(1→2→4→…→30 秒),重连期间自动降级为 REST 轮询,列表不中断
- 60 秒对账:实时在线时仍定期用历史接口核对一遍,兜住重连空档
- 关键词黑白名单:一个开关切换模式,关键词列表可增删,大小写不敏感的子串匹配
- 提示音:有通过筛选的新记录时响一声,用系统通知铃声(静音模式下不响),2 秒节流防连环炸响
- 纯原生界面,不经过 WebView
| 手机 | Android 6.0 及以上(minSdk 23 / targetSdk 23) |
| Home Assistant | 需启用 recorder 组件(默认开启)。历史回溯功能依赖它 |
| 权限 | 仅 INTERNET(安装即授予,无运行时弹窗) |
从 Releases 下载 Android_HA_Viewer-vX.Y.apk,拷到手机,开启「未知来源」后安装。
装好后桌面上显示的名字是「HA Viewer」——仓库名较长,刻意用了短显示名以免在图标下被截断。
在电脑浏览器上:
- 打开 HA → 左下角点你的用户名 → 进入「个人资料」
- 切到「安全」标签页 → 拉到最底部 → 「长期访问令牌」→「创建令牌」
- 名称随意(如
android6)→ 确定 - 弹出
eyJhbGciOiJIUzI1NiIs...一长串 → 立刻复制(关闭后不再显示)
普通权限用户的令牌就够了,不需要管理员。 读状态、读历史、订阅 WebSocket 事件都只要求已认证用户。唯一限管理员的是
POST /api/states/<实体>(直接改写状态),本 App 不使用。但要注意:HA 有实体级权限系统。如果管理员给这个用户限制过可见实体, 请确认该用户对目标实体有
read权限。
首次启动会自动弹出设置页:
| 字段 | 说明 |
|---|---|
| 服务器地址 | 如 http://homeassistant.local:8123。端口不确定就试 8123;新版 HAOS 也可能是 80 |
| 长期访问令牌 | 上面复制的那串 |
| 实体名 | 要监控的实体 ID,如 sensor.xxx |
| 回溯小时数 | 启动时往前读多久的历史,默认 24 |
| 刷新间隔 | 轮询间隔,默认 2 秒。实时在线时此项不生效(改为 60 秒对账) |
| 实时推送 | 默认开。关掉则退回纯轮询 |
| 提示音 | 默认开 |
| 筛选模式 / 关键词 | 见下节 |
填完点「测试连接」可以一次性验证地址、端口、令牌、实体四件事。
一个开关在两种模式间切换,下面是关键词列表:
| 模式 | 行为 |
|---|---|
| 白名单 | 只显示命中任一关键词的记录;列表为空 = 全部显示 |
| 黑名单 | 隐藏命中任一关键词的记录;列表为空 = 不过滤 |
匹配对象是「变化后的值文本」,大小写不敏感、子串匹配。切换模式时关键词保留,只是语义反转。
标题栏会直接显示当前走的是哪条通道:
⚡ 实时—— WebSocket 已连接⏳ 连接中—— 正在建立或重连⏱ 轮询—— 实时不可用,已降级
| 通道 | 环节 | 耗时 |
|---|---|---|
| WebSocket | 事件经网络直接推送,不经过数据库 | 约 50~150 ms |
| REST 轮询 | recorder 落库(commit_interval 默认 5s) + 轮询等待 |
平均约 3.5 s,最坏约 7 s |
为什么轮询这么慢:/api/history/period 读的是 recorder 的数据库,而 HA 默认每 5 秒才把状态变化批量写库(commit_interval)。官方文档说「activity 和 history 不会滞后,因为变化是即时流式推送给它们的」——那说的是 HA 自己的面板,因为面板额外订阅了 WebSocket;REST 客户端享受不到,所以有这层延迟。这也正是本项目要自己实现 WebSocket 的原因。
不需要 Android Studio,不需要 Gradle。
- Python 3.8+
- JDK 8 ~ 17(脚本会自动探测
JAVA_HOME和常见安装位置,也可用HAF1_JDK指定)
为什么用 build-tools 30.0.3:它的
d8在 JDK 8 下就能运行。 build-tools 31+ 的d8要求 JDK 11,会把整条链路的前置要求抬高。
# 0. 取得源码
git clone https://github.com/Xixixiao2007/Android_HA_Viewer.git
cd Android_HA_Viewer
# 1. 下载最小 Android 工具集(build-tools 30.0.3 + android-23,约 119 MB,
# 解压后 236 MB,落在 tools/android-sdk/,已被 .gitignore 排除)
python tools/fetch_sdk.py
# 2. 跑单元测试(58 项:ISO8601 时间解析、黑白名单、WebSocket 帧编解码)
python tools/run_tests.py
# 3. 构建 APK
python tools/build_apk.py --out Android_HA_Viewer.apk一次完整构建约 10 秒。产物默认签名为 debug 密钥(首次构建自动生成 tools/debug.keystore)。
tools/debug.keystore 已被 .gitignore 排除,请勿提交。
原因:这个 App 里保存着你的 HA 长期访问令牌。如果签名私钥公开,任何人都能签一个 包名相同、签名相同的 APK,Android 会把它当作你的应用的更新来安装。
如果丢失密钥,就无法再发布能覆盖安装的更新——请自行备份。
app/
AndroidManifest.xml
res/mipmap-*/ic_launcher.png
src/com/haf1/entitylist/
WsFrame.java RFC 6455 帧编解码(纯逻辑,被单测覆盖)
HaWebSocket.java 握手 / 鉴权 / 订阅 / 事件解析 / 心跳
HaClient.java REST:历史查询、连通性测试、ISO8601 解析(被单测覆盖)
Prefs.java 配置存取 + 黑白名单判定(被单测覆盖)
Notifier.java 提示音
MainActivity.java 列表、实时/轮询双通道调度、重连退避
SettingsActivity.java 设置页
tools/
fetch_sdk.py 下载最小 Android 工具集
build_apk.py 手工 APK 构建流水线
run_tests.py 桌面端单元测试
make_icon.py 生成应用图标(纯标准库)
tztest/ 单测源码与 android/org.json 桩
docs/
USAGE.md 使用说明(含故障排查)
这样构建流水线就不需要生成 R.java,省掉资源符号解析这一整块。代价是界面代码啰嗦一些,
好处是整条构建链路更短、更少出错点。
- 本应用不收集任何数据。所有通信只发生在你的手机与你的 Home Assistant 之间。
- 令牌保存在手机本地的
SharedPreferences中。建议只在局域网内使用明文http://; 如需公网访问,请配置 SSL 或使用 WireGuard / Tailscale 等 VPN。 - 若 HA 对非管理员用户设置了实体级权限限制,本应用只能读取到被授权的实体。