docs(APP_DEV_GUIDE): 第三方开发者上手指南 + 校订 SDK 头状态行 + README 索引

新增 docs/APP_DEV_GUIDE.md(从零到上架动线:环境→最小App→工程结构→
构建打包→签名安装→部署调试→SDK能力速查→红线→FAQ),全部以仓库现状为准;
修正 hello app.json 全字段表(framework/immersive/background_audio/depends/
icon/version+version_code)、meow_audio_*/通知/媒体会话/字体档位等事实。
同步:MEOW_APP_SDK.md 头部「设计稿→实现版」状态行(只动状态行,正文不动);
README.md 文档索引加该指南链接。

说明:hello_app 实际无独立 app.json(包装到 MeowAPPs/<pkg>/ 根目录),指南按真实
打包形态描述;nyai 无独立 pack 命令,出包用 nyai export 或 tar czf;.mpk 须在 tar 根。
This commit is contained in:
MeowOS committed 2026-09-01 19:38:52 +08:00
1 parent 1e97dbf54a
commit 7e7e45ae26
3 files changed
+469 -1

No files matched your search

+467
View File
@@ -0,0 +1,467 @@
# MeowOS 第三方 App 开发指南(APP_DEV_GUIDE)
> 受众:想在 R3 Pro II / MeowOS 上发布自己 App 的第三方开发者(非 MeowOS 系统组)。
> 从零到上架动线:**环境 → 最小 App → 工程结构 → 构建打包 → 签名安装 → 部署调试 → SDK 能力 → 红线 → FAQ**。
> 权威口径:本文所有 API 以 `patch_v2/sdk/meow_ui.h` 为准;规范以 `MEOW_APP_SDK.md` / `APPS.md` / `docs/MEOWOS_SPEC_REF.md` 为准。凡与 `meow_ui.h` 不一致,以头文件为准。
> 配套文档:`MEOW_APP_SDK.md`(SDK 设计全量)、`APPS.md`(加载器/生命周期)、`docs/MEOWOS_SPEC_REF.md`(速查)、`NOTIFICATION_SYSTEM.md`(通知)、`SYSTEM_SETTINGS.md`(设置)。
---
## 0. 一图看懂:App 是什么
一个 MeowOS App = **一个静态 MIPS32r2 ELF 二进制** + 一份 `app.json` 描述 + 若干资源,打包成 `.mpk`(= tar.gz),装到 TF 卡 `MeowAPPs/<package>/` 下。二进制**链接 `meow_ui.a` 静态库**,通过 `meow_ui.h` 声明的一套 API 拿屏幕、输入、控件、主题、字体、通知、音频——**绝不**自己 `open("/dev/fb0")` / `mmap` / `ioctl` / `open("/dev/input/event*")`。
```
┌──────────────────────────────┐
│ MeowAPP(你的业务,只调 meow_ui API)│
├──────────────────────────────┤
│ meow_ui.a(静态库,含 meow_font) │
│ 绘制/控件/图片/输入/主题/字体/通知/音频│
├──────────────────────────────┤
│ MeowOS 系统(launcher/amsd/audiod)│
└──────────────────────────────┘
```
设备:480×720、RGB565(代码里颜色统一 24-bit `0xRRGGBB`,SDK 内部转 RGB565);文本字号约定 **28**(标题/状态栏)/ **20**(正文/按钮)。
---
## 1. 开发环境准备
### 1.1 交叉工具链
仓库自带了 mipsel musl 静态交叉工具链,直接用,不用自己装:
```sh
$HOME/Project/R3ProII/.tools/zig-mipsel-cc # zig cc 包装(--target=mipsel-linux-musleabi, soft-float)
$HOME/Project/R3ProII/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf/ # sysroot(含 libz.a 等)
```
编译一个静态 MIPS 二进制(参考 `patch_v2/hello_app/build_hello.sh`):
```sh
ROOT="$HOME/Project/R3ProII"
CC="$ROOT/.tools/zig-mipsel-cc"
SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf"
"$CC" -Os -static -Wall -Wextra -I"$ROOT/patch_v2/sdk" -I. \
app.c ui/*.c logic/*.c \
"$ROOT/patch_v2/sdk/meow_ui.a" "$SYSROOT/lib/libz.a" -lpthread -o myapp
```
要点:
- 必须 `-static`(加载器只跑静态 MIPS32r2 ELF,软/硬浮点均可,见 `APPS.md §2.1`)。
- 链 `meow_ui.a`(`build_sdk.sh` 已把 `meow_font.o` 打进库里)+ `libz.a`(meow_font 的 inflate 依赖)+ `-lpthread`。
- `-I"$ROOT/patch_v2/sdk"` 引 `meow_ui.h`/`meow_ui_view.h` 等公共头;`-I.` 引你自己工程的头。
- 期望 `-Wall -Wextra` 零警告(参照 hello)。
### 1.2 宿主编译(离屏逻辑测试)
SDK 依赖板载 `/dev/fb0` 与字库,宿主机没有——但你可以**链真实 SDK 目标文件 + 打桩**来跑逻辑单测(参考 `patch_v2/sdk/test_host_stub.c`):
- `test_host_stub.c` 提供了 `meow_font_*` / `meow_ui_global_render` 等宿主桩(无 fb → 5×7 点阵回退;无字库 → `meow_font_load` 返回 -1)。
- 宿主可 `gcc` 直接编译 SDK 各 `.c` + 你的逻辑,跑纯 C 断言,**不碰硬件**(`hello` 的 `test_p1p2p3_repro` / `test_meow_ui_widgets` / `test_pinyin` 等都是这么跑的,宿主宿主验证全 PASS)。
- 注意:`logic/` 层要写**纯 C**(不依赖 `meow_ui.h`),才能裸宿主单测;`ui/` 层才碰 SDK。
### 1.3 qemu-mipsel 全系统仿真
需要跑全链路(含内核/fb)时可用 `qemu_env/scripts/run_halley6.sh`(Ingenic X1600E 全系统仿真):
```sh
qemu_env/scripts/run_halley6.sh # 引导内核到串口控制台(默认)
qemu_env/scripts/run_halley6.sh -usbrootfs # 附加 USB rootfs 镜像
```
注意:必须带 `mem=64M`(脚本已内置,去掉会 early hang);`-usbrootfs` 的 USB 数据通路在 QEMU 里仍不完整(设备未枚举),rootfs 引导会失败——多数情况下它在宿主编译 + 真机验证之间用不上,**不是常规提交流水线**。
### 1.4 真机前置
- 出站靠 TF 卡;确保卡上至少有 `MeowAPPs/`(放 App)与 `MeowData/`(App 数据,首启自动建)。
- 设备跑 MeowOS 模式(默认)。看 `MEOWOS_SPEC_REF.md §5.4` 确认固件版本单点 `/etc/meowos_version`。
---
## 2. 五分钟最小 App(以 hello 为模板)
最快路径:拷贝 `patch_v2/hello_app/` 改包名与文案。逐文件讲。
### 2.1 app.json 全字段表
`app.json` **必填**,放 `MeowAPPs/<package>/` 根目录。参考示例(`patch_v2/apps_assets/apps/os.meow.hello/app.json`):
```json
{"name":"Hello App","exec":"/usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/hello",
"package":"os.meow.hello","version":"0.1","framework":1,"immersive":1,"depends":"curl:optional"}
```
| 字段 | 必填 | 说明 |
|---|---|---|
| `name` | ✓ | 列表显示名。**当前仅 ASCII**(UTF-8 中文留待 v3,需完整 UTF-8→UTF-16 转换) |
| `exec` | ✓ | 可执行文件**绝对路径**,必须是**静态** MIPS32r2 ELF(指向 `MeowAPPs/<package>/` 内二进制) |
| `package` | — | 反域名权威标识(= app_id = 目录名 = `.mpk` 文件名茎,三者必须一致)。**`os.meow.*` 为系统保留前缀**(见 §5),第三方用自有前缀(如 `dev.*`)。缺失回退目录名;两者存在且不同 → WARN |
| `version` | — | 显示字符串(versionName 角色,如 `1.2.3-beta`,**不参与比较**) |
| `version_code` | — | **纯数字,安装/升级比较的唯一依据**;缺失 → 拒装;新版 < 已装 → 拒装(防降级),相等 → 允许重装。见 §5 |
| `author` | — | 保留字段 |
| `framework` | — | `1`=SDK 框架 App(用 `meow_ui.a`;**推荐**)。`0`/不写=裸自绘(不推荐,见红线) |
| `immersive` | — | `1`=沉浸。**注意(M2.89+)**:看门狗 `SIGUSR1` 只发给「**裸沉浸**」App(非 `framework=1`);SDK 框架 App 的返回走 `on_swipe`,别把 `SIGUSR1` 当返回键 |
| `background` | — | M4:无头后台服务注册权限(`service.<svc>` 服务二进制 = App 同目录 `<svc>` 文件) |
| `background_audio` | — | M4:后台播放保活(进程不冻结 + UI 冻结复用 §10.5 自抑);播放类 App 声明。如 `dev.player.ncm` 已声明 `background_audio=1` |
| `fullscreen` | — | app 级全屏:状态栏**收合至提示线** + 保留左缘返回/顶部提示线/下滑通知中心。activity 级用 `route.<id>.fullscreen` |
| `fonts` | — | 字库档位声明:`"full"` / `"gbk"` / `"common"`(默认 `common`≈4k;需大字库声明 `"full"`)。**注意**与 `"fonts":["<handle>"]`(应用自带字库句柄)同名二义,见 `MEOWOS_SPEC_REF §7 A-14` |
| `depends` | — | **二进制依赖声明**:`"curl,jq:optional,ffmpeg:required"`(JSON 值串)或行式 `depends=curl,jq:optional,ffmpeg:required`。逗号分项,每项 `<name>[:<mod>]`;无标注默认 required,也接受 `:opt`/`:req`;未知修饰符按 required 并 WARN。启动前查板载 `/usr/bin/<name>` 或卡上 `/usr/data/mnt/sd_0/MeowOS/opt/bin/<name>`(X_OK,命一即可)。required 缺失 → **不启动**(toast「缺依赖: <name>」);optional 缺失 → 仅 WARN 放行 |
| `icon` | — | 预留:应用列表图标 |
> ⚠️ 解析器极简(launcher/nyai 内实现):只匹配 `"name":`/`"exec":`/`"package":`/`framework`/`immersive`/`route.*`/`awake.*`/`service.*`/`background`/`background_audio`/`fullscreen`/`fonts`/`depends`,**不做 JSON 校验**(多行/单行均可),但字段名和引号写法要规范。
### 2.2 main 入口:SDK 初始化 + 事件循环 + 退出约定
看 `patch_v2/hello_app/app.c`,核心骨架如下:
```c
#include "meow_ui.h"
#include "meow_ui_nav.h"
#include "meow_ui_media.h"
#include <signal.h>
static void on_sigterm(int s) { (void)s; meow_ui_request_exit(); } /* 干净退出:不画 BYE 直接退 */
int main(void)
{
meow_log(MEOW_LOG_INFO, "start");
/* ① SDk 初始化:开 fb / 读几何 / 载字库 / 建 back_buf。opts 宽高 0=自动读,宿主导跑用 480x720 */
meow_ui_opts o = { 480, 720 };
if (meow_ui_init(&o) != 0) { meow_log(MEOW_LOG_ERROR, "init failed"); return 1; }
/* ② 主题监听 + M3 导航上报 + 媒体会话(可选,但推荐接线) */
meow_theme_add_listener(NULL);
meow_ui_nav_init(); /* 读 MEOW_ACTIVITY / MEOW_CHAIN 初始化导航栈 */
meow_ui_media_init(NULL); /* app_id = env MEOW_LOG_APP */
meow_ui_media_on_playctl(demo_media_playctl);
signal(SIGTERM, on_sigterm); /* 退出兜底:SIGTERM → request_exit */
/* ③ 建各页 view(on_create 建控件/状态)+ 弹层实例(dialog/sheet 建一次,复用)*/
meow_view *home = home_view_create();
meow_view *full = full_view_create();
meow_view *demo = demo_view_create();
/* ④ 进入根页:activity_push + home on_enter → home_redraw */
meow_view_page_push(ACT_HOME);
/* ⑤ 事件循环回调表(on_key 物理键是系统语义,SDK 不下发业务确认/exit)*/
meow_ui_events ev = { app_on_key, app_on_tap, app_on_swipe, app_on_tick,
app_on_swipe_progress, app_on_touch_down, app_on_touch_move };
meow_ui_run(&ev); /* 阻塞,直到 meow_ui_request_exit() */
/* ⑥ 清理:释放 view(触发各页 on_destroy)+ 弹层 + shutdown */
meow_view_free(full); meow_view_free(demo); meow_view_free(home);
meow_dialog_free(g_dlg); meow_sheet_free(g_sheet);
meow_ui_shutdown();
meow_log(MEOW_LOG_INFO, "exit");
return 0;
}
```
**退出约定**(`APPS.md §3.3`):
- 任意退出码均可,`exit(0/1)` 即可,**不要 `abort()` 留僵尸**——守护进程 `waitpid` 立即返回并恢复播放器。
- 用 `meow_ui_request_exit()` 或**根页左缘左滑**退出;**绝不监听物理键映射 exit**(见 §8 红线)。
- 不需要「恢复/清屏」——应用退出后系统恢复原界面,你清屏反而闪一下。
### 2.3 事件分发
`meow_ui_run` 给的平铺回调,在 `app.c` 里路由到当前页(见 `app.c` 的 `app_on_tap`/`app_on_swipe`/`app_on_tick`/`app_on_touch_*`)。典型分法:
- `on_tap` → 命中当前页控件(`meow_widget_hit` / `meow_listview_hit` / `meow_topappbar_hit` 等)。
- `on_swipe` → `LEFT`=返回(`meow_view_page_pop`,根页则 `meow_ui_request_exit`);`UP/DOWN`=滚动。
- `on_touch_down`/`on_touch_move` → 跟手拖动(slider / 列表 / 页面滚动)。
- `on_tick` → 动画/时钟(如进度条步进)。
- **`on_key` 仅供 SDK 内部状态,不含电源/音量/播放的确认/exit 语义**——别把它当输入。
- 左缘返回手势:边沿上报 `on_swipe_progress`,SDK 自动画顶部进度条(`meow_ui_swipe_hint`)。
---
## 3. 工程结构约定
hello 的分层(`hello_internal.h` 里写得很清楚),建议照抄:
```
myapp/
├── app.c # main + 装配 + 事件分发(把 meow_ui_run 回调路由到各页)
├── myapp_internal.h # 跨 TU 共享声明(只 extern 声明,不定义)
├── ui/
│ ├── home/home.c # 每页一个 view 文件(持本页视图态)
│ ├── home/home.h
│ ├── demo/demo.c
│ ├── list/list.c # 页面内 CONTAINER 子视图(列表区块)
│ ├── full.c
│ └── common/chrome.c # 共享 chrome(TopAppBar / NavigationBar / FAB)
└── logic/
├── logic.h # 纯 C 接口(不依赖 meow_ui.h)
├── state.c # 计数/状态业务 + getter/setter
└── notify.c # 通知业务(此处才 include meow_ui.h 用 SDK 通知 API)
```
**分层契约**(`logic/logic.h`):
- ① UI→业务:只调 `logic_*` / `notify_*` 纯函数命令/getter,业务**不回调 UI**。
- ② 业务→UI:业务态变化经 `logic_set_listener(cb, ctx)` 的**可选 push 监听器**通知 UI 刷新(`home_state_listener` 示例)。
- ③ 每页 `meow_view` 的 `ctx` = **视图控制器**(biz-UI 绑定点),页面后续可把这层状态吸进控制器。
**视图框架 `meow_view`**(`patch_v2/sdk/meow_ui_view.h`):
- 生命周期钩子:`on_create → on_enter → (事件循环) → on_leave → on_destroy`,另有 `on_layout`/`on_draw`/`on_event`。均可 NULL。
- **页面即文件**:每页一个 `meow_view_create(id, MEOW_VIEW_ACTIVITY, &hooks, &ctrl)`,`id` 是你命名的稳定值(`ACT_HOME=0`/`ACT_FULL=1`/`ACT_DEMO=2`)。
- 页面切换统一走 `meow_view_page_push/pop/replace`(包装 activity 栈,**on_enter/on_leave 唯一入口**),App 不再分别调 `activity_push/pop`。
- 子视图嵌套:`meow_view_add_child` 挂 CONTAINER 子 view(如 demo 页的列表区块 `list.c`),嵌套生命周期由 SDK 递归驱动(随父页进出),无需 App 显式接管。
- `on_draw` 为回调、view 不缓存像素(非保留);重绘由 `meow_ui_invalidate` 触发。
- `meow_view_dispatch_event` 是「把语义事件注入当前页谓词子树路由」的入口(先子>父按 z 降序,命中即消费);未接线于 `meow_ui_run`,你可在 `on_tap`/`on_swipe` 里改用它做 SDK 原生路由。
---
## 4. 构建与打包
### 4.1 链接 meow_ui.a
见 §1.1 命令。`meow_ui.a` 由 `patch_v2/sdk/build_sdk.sh` 构建(内含 `meow_font.o`),改 SDK 源码后**重编 meow_ui.a 必须同步重编你的 App**(结构变化即 ABI 变化,见 `MEOWOS_SPEC_REF §3.4`)。
### 4.2 `.mpk` = tar.gz
`.mpk` 内容 = `app.json` + 二进制 + 资源(**不含 data/cache**,运行时按设备生成)。包名 = `.mpk` 文件名茎 = `app.json.package`(如 `os.meow.hello` → `os.meow.hello.mpk`)。
> **nyai 无独立 `pack` 命令**。两种出包:
> 1. **手工打包**(从 staging 目录,`meow_sign/README §2.2` 的口径):
> ```sh
> mkdir -p build/os.meow.xxx
> # 放 app.json + meow 二进制 + 资源;app.json.package == 目录名
> tar czf os.meow.xxx.mpk os.meow.xxx # .mpk 须在 tar 根(无 <pkg>/ 包裹 → 换包时注意)
> ```
> 2. **导出已装应用**:设备上 `nyai export <pkg> [out.mpk]`(打 `MeowAPPs/<pkg>/`,排掉 `*.pre_*`/`*.bak_*`/`logs/` 等运行残留)。
`.mpk` 内部结构:
```
os.meow.xxx.mpk (tar.gz)
├── app.json # package/exec/version(+version_code)/framework/immersive/background_audio/...
├── meow # 静态 MIPS32r2 ELF(exec 指向)
├── <资源文件> # 只读程序资源
└── <signature footer> # 仅签名过的包有(Ed25519 v2 184B;尾部追加)
```
---
## 5. 签名与安装分级
### 5.1 分级规则(`APPS.md 附录 §3` / 签名设计)
| 包 | 规则 |
|---|---|
| **`os.meow.*`(系统保留前缀)** | **必须有有效签名**;无签 / 验签失败 → 一律拒装(防伪造系统包名) |
| 第三方(自有前缀如 `dev.*`) | 无签可**直接装**(安装时告警);**有签必须验签**(失败拒装) |
| 所有包 | 验签 + **三重绑定** + **防降级**(见下) |
**防降级**:只看 `version_code`(纯数字)。新版 code < 已装 → 拒装;相等 → 允许重装;**code 缺失 → 拒装**(提示缺 version_code)。`version` 仅显示,不比较。
**三重绑定**(三方各司其职):
1. 签名 footer 的 `package` == `.mpk` 文件名茎(`meow_verify` 校验)——防改名。
2. 签名 footer 的 `package` == 解包 `app.json.package`(nyai / launcher 安装流校验)——防改 app.json。
3. `os.meow.*` 前缀 + 必须验签通过(nyai / launcher 安装流)——防无签冒充系统包。
### 5.2 开发者自签名流程(不泄露任何密钥材料)
> 工具:`patch_v2/meow_sign/meow_sign`(**宿主端、离线维护机**,不落设备、设备只持公钥)。私钥**永不入库、永不落设备**。设备侧把关的是固件 `/usr/bin/meow_verify`(内嵌公钥数组,任一公钥通过即放行)。
```sh
# ① 首次:生成/备份密钥(仅一次;若 .keys/signing.key 已存在,直接复用,勿覆盖——覆盖会令旧包全部失效)
./patch_v2/meow_sign/meow_sign keygen .keys/signing.key
# 私钥 .keys/signing.key(0600) 离线备份;公钥 .keys/signing.key.pub 可入库/固化进固件
# ② 打包(不含签名,纯 tar.gz;app.json.package 必须 == 目录名 == .mpk 文件名茎;version_code 单调递增)
tar czf os.meow.xxx.mpk os.meow.xxx
# ③ 签名(默认 v2 含被签时间戳)
./patch_v2/meow_sign/meow_sign sign os.meow.xxx.mpk .keys/signing.key
# ④ 产签后自测(推荐)
./patch_v2/meow_sign/meow_sign verify os.meow.xxx.mpk .keys/signing.key.pub
./patch_v2/meow_verify/meow_verify_host os.meow.xxx.mpk # 用固件内嵌公钥,与设备同口径
# ⑤ 装到设备:把 .mpk 放卡上 → nyai install
```
要点:
- `sign` 的 `.mpk` 文件名茎**必须等于包名**(否则 `meow_verify` 二次校验拒)。
- v2 块(184B,含时间戳)需 **2B+ 固件**;`--v1` 产物(180B)兼容旧 2A 固件——固件与工具链同版本发布即可避免兼容坑。
- 签名是**防误装 / 防伪造包名 / 防沿途篡改**,不是安全边界(见 `meow_sign/README §0`,诚实标注)。
- **本仓库 `.keys/signing.key` 是系统主钥**——第三方开发者**不要用它**,用自己的密钥对(否则无法区分来源,且泄露私钥等于放弃系统包信任)。你自己的私钥离线备份,公钥按需提交给设备厂商固化进 `meow_verify` 公钥数组(或走公开渠道分发)。
### 5.3 安装
- 设备端:`nyai install <pkg或文件.mpk>`(固件内置 `/usr/bin/nyai` 优先,或卡上 `MeowOS/opt/bin/nyai`)。同名 = 更新(旧目录备份 `.bak_<pid>`,`MeowData/<pkg>/{data,cache}` 保留,成功自清 `.bak`)。
- `nyai list` / `nyai info <pkg>` / `nyai remove <pkg>`(数据默认保留,`--purge` 连 MeowData 删)。
---
## 6. 部署与调试
### 6.1 TF 卡目录
```
/usr/data/mnt/sd_0/
├── MeowAPPs/<package>/ # app.json + meow 二进制 + 资源(目录名 = app.json.package)
├── MeowData/<package>/{data,cache} # App 数据(首启自动建;exFAT 无软链,用 env 注入)
└── MeowOS/logs/<package>/ # App 日志(每次启动新文件 <ts>_<pid>.log)
```
启动时注入 env(SDK `meow_config_*`/`meow_ui_nav_init`/日志都读它):
`MEOW_DATA_DIR` / `MEOW_CACHE_DIR` / `MEOW_LOG_DIR` / `MEOW_LOG_APP`(=package) / `MEOW_LOG_LEVEL` / `MEOW_ACTIVITY` / `MEOW_CHAIN`(+ `MEOW_FONT_PNG`/`MEOW_FONT_IDX` 当前生效字体、`MEOW_FULLSCREEN`)。
### 6.2 面板上传
MeowPanel(外置 TF 卡 Web 面板,8080)可 `nyai install/export/remove`。**⚠️ 面板鉴权默认关**(内网信任模型)——`reboot/shutdown/ota/apps/删除/上传` 危险端点勿暴露公网。
### 6.3 am CLI(活动管理器)
`am` 是 amsd 的用户侧 CLI(固件 `/usr/bin/am`),连 `/usr/data/meow_ams.sock`(`--sock` / `env MEOW_AMS_SOCK` 覆盖):
```
am status # 在跑 App 表(pkg/状态/pid/运行时长/activity)
am list # 注册表 + 状态(pkg/name/state/pid/launches/crashes)
am history [pkg] # 事件历史(每 App 最近 8 条)
am start <pkg>[/<act>] # 拉起(root)
am kill <pkg> # 终止(SIGTERM 语义,root)
am rescan # 手动触发注册表重扫(卡刚挂载/热装后立即可见)
am resolve <pkg> <activity> # 路由决策(返回 CHAIN)
```
写操作(start/kill)**仅 root**。**已知坑(agent-250)**:开机早于卡挂载 → amsd 注册表扫空 + 永不重扫 → `am start` 报 `not registered`——用 `am rescan` 或等 3s 自愈/30s 合并重扫。
### 6.4 日志
- **App 日志**:`meow_log(lvl, fmt, ...)` 一行;SDK 写 `MeowOS/logs/<package>/<ts>_<pid>.log`(每次启动新文件,单文件 ≥2MB 自动续新,每 App 总量上限,无卡降级 stderr 不崩)。级别:`MEOW_LOG_DEBUG/INFO/WARN/ERROR`;`meow_log_set_level` 可选,默认 `INFO`。
- **崩溃日志**:系统级 `meow_crash` 捕获 + App 崩溃直报。真机看 `/usr/data/meow_*` 与 `MeowOS/logs/`。
- **离线跑**:`meow_launcherd --scan` 列应用;宿主跑逻辑单测看 stdout。
---
## 7. SDK 能力速查表
(完整签名以 `patch_v2/sdk/meow_ui.h` 为准——下面每行都可交叉印证。)
### 7.1 控件库清单(一句话)
| 控件 | 一句 |
|---|---|
| `meow_btn_create`(Filled)/ `meow_btn_create_variant`(五型:FILLED/TONAL/OUTLINED/TEXT/ELEVATED) | 按钮;命中用 `meow_widget_hit`,选中/禁用走状态机 |
| `meow_slider_create` / `meow_switch_create` | 滑条/开关;`set/get` 取值 |
| `meow_list_create`(label-only)/ `meow_lazycolumn_create`/`meow_lazyrow_create` | 数据源回调 + 可见区虚拟化;`on_swipe` 里 `meow_list_scroll_by` 驱动滚动 |
| `meow_listview_create`(`W_LISTVIEW` 结构化列表) | 行=图标+标题+副标题+右侧操作+选中;`meow_listview_hit`/`meow_listview_tap_at` |
| `meow_textfield_create`(接 IME)+ `meow_textfield_activate` | 单行文本输入(阻塞 IME) |
| `meow_card_create` + `meow_card_set_content` / `meow_divider_create` / `meow_badge_create` | 卡片/分隔线/角标 |
| `meow_checkbox_create` / `meow_radio_create` | 勾选/单选(走 set_selected) |
| `meow_progress_create`(线性)/ `meow_progress_circular_create`(圆形) | 进度条(0-100,`-1`=不定) |
| `meow_fab_create` | 悬浮按钮 |
| `meow_topappbar_create`(标题+返回+动作)/ `meow_navigationbar_create`(底部导航)/ `meow_tabbar_create`(顶部分页) | 标题栏/导航栏/页签 |
| `meow_dialog_create`(CONFIRM/SELECT/INPUT/INFO)/ `meow_sheet_create`(BottomSheet) | 阻塞模态;点外/左缘返回=取消 |
| `meow_row_create`/`meow_column_create`/`meow_box_create` + `meow_layout_add`/`arrange` + `meow_layout_spacer` | 组合布局(固定尺寸子项) |
| `meow_widget_custom_create` | 自定义画布:App 给 render/hit 回调(同 `meow_widget`) |
| 通用 | `meow_widget_set_opts`(Modifier 式 Options)、状态机 `meow_widget_set_state`(ENABLED/SELECTED/FOCUSED/PRESSED)、`meow_widget_set_z(999)` 置顶、`meow_draw_custom_register` 注册非控件直绘 |
### 7.2 主题(MD3 / MIUIx × 明暗——适配义务)
- 主题 = **顶级风格族 `ui.style`(`MEOW_STYLE_MD3`/`MEOW_STYLE_MIUIx`)× 明暗 `display.theme`(light/dark/auto)**。
- **适配义务**:用 `meow_theme_get()` 取的**语义色**(`primary`/`surface`/`surfaceContainer`/`outline`/`bg`/`fg`/`accent`/`dim`…),**不要硬编码颜色**;明暗都要能看(`AUTO` 会来回切)。当前 MIUIx 仅换配色(状态栏图标未 per-style 换表,是已知缺口)。
- 字号约定:`font_title=28` / `font_body=20`(`meow_theme` 给默认)。
- 界面底 `meow_ui_set_page_bg(th->bg)`;状态栏高度 `meow_ui_statusbar_height()`(非全屏=28,全屏=0)用于内容 inset。
### 7.3 字体档位声明 + 应用字库接口
- 档位:`MEOW_FONT_TIER_COMMON`(默认 ≈4k)/ `GBK`(≈20k+)/ `FULL`(全字符集)。App 需大字库时 `app.json "fonts":"full"`,launcher 为你加载大字库。
- SDK:`meow_font_set_tier` / `meow_font_tier`;`meow_font_get_available_count` / `meow_font_enumerate`(读 `MeowOS/config/fonts/index`)/ `meow_font_select_by_id`(App 会话内固定用某套;丢失降级默认 + WARN)。
- **判缺字**:`meow_font_has_glyph(cp)` 预判当前字库是否含该码位——缺字自行回退/提示。
- 注意:App **未自带字库**则跟随系统 `font.file`(launcher 以 `MEOW_FONT_PNG`/`MEOW_FONT_IDX` env 注入);应用自带字库(`load_handle`)为**草案/未实现**。
### 7.4 IME(输入法)
- 模式:`MEOW_IME_ASCII` / `MEOW_IME_NUM` / `MEOW_IME_PINYIN`(中文拼音)。
- 阻塞:`meow_ui_text_input(ctx)`(回车=提交+关闭、ESC/点背景=取消);`meow_textfield_activate` 聚焦并打开阻塞 IME。
- 非阻塞:`meow_ui_text_input_async` + `meow_ime_active()`/`meow_ime_text()`/`meow_ime_composition()`(App 在 `on_tick` 里轮询)。
- **坑**:阻塞 IME 的内层轮询必须喂 `meow_ui_activity_ping`(SDK 已做)否则进度条/息屏卡死;键盘区背景快照/恢复(防残影);回显条是**实时输入回显**非候选,候选框留中文。
### 7.5 音频(meow_audio_*,独占 audiod)
- 链路:`meow_audio_open(rate, ch, fmt)` → 返回 `stream_id > 0` → `meow_audio_write(pcm, frames)`(**阻塞**,socket 背压)→ `meow_audio_pause(sid, paused)` → `meow_audio_close(sid)`。
- audiod 独占 `hw:0,0`;**后开流赢**,旧流收 `MSG_AUDIO_PREEMPTED` → 你的 `on_preempt` 回调 → 之后 `write` 返回负值,应重新 `open`。
- **audiod 不在 → `open` 返回 `MEOW_ERR_NOENT`(负值),不做隐式直开回退**;`MEOW_AUDIO_DIRECT=1` 仅调试用别依赖。
- `meow_audio_query(&state)` 读快照(活跃流/采样率/underrun/路由口/音量);`meow_audio_on_preempt(cb, ctx)` 注册被抢占回调。
- DAC 原生率跟随、无 SRC;格式归一(S16 直通 / S32 升位 / mono 复制);4.4/3.5 路由随插孔。
- 播放类 App 记得 `background_audio=1` + 媒体会话上报(见 §7.7)。
### 7.6 通知(meow_notify_* / MSG_NOTIFY)
- 声明:`meow_notify_register_type(cat)`(每 App ≤4 类型);发:`meow_notify_send(&n)`(返回 id);消:`meow_notify_cancel(id)`;进度:`meow_notify_update_progress(id, p)`。
- 配额:**每 App ≤4 类型;每类型活动 ≤4(满丢最旧);媒体单例(每 App 1 条,重发替换)**。
- `timeout_ms`:`>0`=过期毫秒数;`0`=不过期(直到 cancel);`-1`(ONGOING)=常驻。
- 负载 `meow_notify{n}`:`category`/`urgency`/`title`/`body`/`icon`/`timeout_ms`/`target_activity`(点击跳转 Activity 名,M3 导航预留)/`progress`。字段对齐 freedesktop `Notify()`。
- 走 IPC `MSG_NOTIFY`;10s 后自动转储到 TF `MeowOS/notifications/`(ring 10MB)。当前 SDK 内部维护活动表 + 日志落点;真实投递到通知中心 UI(launcher 渲染)为已落地链路。
### 7.7 媒体会话(meow_ui_media_*)
- `meow_ui_media_init(app_id)` / `meow_ui_media_on_playctl(cb)`(注册播放控制并启动后台接收线程)/ `meow_ui_media_meta(state, pos_ms, dur_ms, title, artist, album, artwork, lyric)`(上报元数据)/ `meow_ui_media_stop()`。
- 控制回调 `playctl(cmd, seek_ms)`:cmd `1`=播放 `2`=暂停 `3`=上一首 `4`=下一首 `5`=seek `6`=播放/暂停 toggle(物理播放键 164 恒发 6,由你对当前态翻转——**源是状态权威**)。物理上下曲:`meow_ui_media_sys_key(code)`。
- 声明 `MF_PLAYBACK` 的 App 首次上报 → 注册为媒体源;通知中心媒体卡片 / 主界面 Now Playing 据此显示。
- best-effort:无 launcher/socket 静默降级,绝不崩。
### 7.8 后台播放声明
- `app.json "background_audio":1`(M4 已落地):进程**不解冻** + **UI 冻结**复用 `§10.5 自抑`(比任务书 SIGSTOP 方案优)。launcher 有 `meow_bgplay` 保活表(退避 1/2/4/10s + 熔断 + 广播);应用列表标注「·后台」。依赖 `background` 字段 + `service.<svc>`。
- 声明后台播放后:无头时仍喂媒体会话 + 可继续 `meow_audio_*` 出声;退出时停线程/关流。
### 7.9 应用配置(meow_config_*)
- App 自有键值偏好,落 TF 数据目录(`MEOW_DATA_DIR`,无卡回退 `/tmp/meowcfg_<pid>`),**不占板载、无 schema**(与系统 `meow_settings_*` 区分)。
- `meow_config_set/get/int/bool/remove`;写 `<data>/<sanitized_key>.cfg`(key 只允许 `[A-Za-z0-9._-]`,上限 64)。返回码 `MEOW_CFG_OK(0)` / `NOENT(-2)` 等。
### 7.10 网络注意事项
- App 一般不需要联网;确需联网时**克制请求**(见 §8 风控式节制)。
- 系统层面(`netmgrd` 期 0/1):无 iptables 用户态 / 无 cgroup / 无 conntrack → **内核级每应用管控不可行**;主杠杆 = DNS 代理(域名级)+ 连接普查(inet_diag)+ **SDK 自愿通道**。App 走 SDK 自愿通道配合管控是后续方向;当前 App 如联网,请自律、尊重断网降级。
---
## 8. 规范与红线
> 详见 `APPS.md §1.3 / §3.3 / §3.4.1`、`MEOWOS_SPEC_REF §2.4 / §5.1`。以下为最要紧的违约原因。
1. **物理键禁映射返回/确认/OK/翻页**。物理键是系统资源:电源=息屏/亮屏/长按菜单/8s 强关,音量=调音量,播放/上下曲=媒体控制,返回=**触屏左滑**(`MEOW_SWIPE_LEFT`)/虚拟返回(非物理键)。**绝不用 `on_key(POWER)→exit` 这类映射**;`on_key` 仅供 SDK 内部状态。界面导航一律走触摸(返回钮/左缘左滑)。
2. **局部脏区刷新,别整屏重画**。控件/区域变化用 `meow_ui_invalidate(x,y,w,h)`(清底 + 按 z 重画相交绘制物 + 原子上屏),别 `meow_ui_flush`/整帧重画。**非控件直绘内容**(`meow_ui_text`/`meow_ui_fill_rect` 等)**必须 `meow_draw_custom_register` 注册**(声明 bbox + 绘制回调 + z),否则区域重画清底后会漏画(hello demo 的分区标题/slider 值文本就是典型注册示例)。滚动列表的视口裁剪由 SDK 全局负责(`W_LIST` 只画视口内行),别在 App 侧再做整屏。
3. **内存预算**。设备内存很紧(QEMU `mem=64M` 参考,rootfs ≤45MB)。避免大块分配、避免大字库双驻留(大字库按档装载、卸载旧 atlas)、别把卡上大文件 mmap 当运行必需——**运行期别依赖卡上资源**(见 §9 卡移除)。
4. **板载存储别乱写**。板载 `/usr/data` 只剩 ~10MB,是稳定框架 + KB 级设置。**App 数据一律写 `MeowData/<pkg>/`(TF,经 `MEOW_DATA_DIR`/`MEOW_CACHE_DIR` env)**;绝**不碰 NAND 分区 / mtd / `/data/recovery_all`**(恢复出厂,红线)。
5. **系统动作经专项服务**。App/组件不直连 `sys_do_reboot`、不写 NAND/mtd5;关机/重启/升级/回 HiBy 一律经 `meow_svc_power`(IPC)。App 一般无需做,但别绕过。
6. **风控式网络请求节制**。避免高频轮询/无限重试/后台自灌;尊重断网/弱网(优雅降级而非死循环/崩溃);与系统 netmgrd 管控共存(域名级,非内核 per-app)。
7. **别 destroy 别占用播放器/声卡**。不要 `killall hiby_player`(触发看门狗整机重启);不要 fork 长驻后台进程(退出要「真正退出」,否则 SIGCONT 后仍占屏抢输入)。
8. **`app.json` 别缺失 `version_code`**(拒装);别用 `os.meow.*` 前缀(除非你是系统签名 App)。
---
## 9. FAQ / 常见坑(真踩过的)
1. **脏区透传:局部刷新抹掉上层 chrome**。症状:拖动/点某个控件后,上层 FAB/标题栏被抹掉。根因:App 侧旧的「清底 + 只画单个控件」刷新。**修法**:改走 SDK `meow_ui_invalidate`(z 栈重画下层+上层),非控件直绘**注册 CUSTOM**(`meow_draw_custom_register`),否则清底后漏画。`meow_ui_paint_full()` 整帧按 z 重画兜底。
2. **IME 阻塞不喂 on_tick → 进度条/息屏卡死**。阻塞 `meow_ui_text_input`/`meow_textfield_activate` 的内层轮询必须喂 `meow_ui_activity_ping`(SDK 已做);键盘区用背景快照/恢复防残影;回显条是实时输入回显非候选。
3. **字体声明丢失 → 降级小字库**。`app.json "fonts":"full"` 丢了 / 大写不符 / 该档没生成 → 按 `common` 装载,生僻字缺字(`meow_font_has_glyph` 预判,回退或提示)。字体三件套(`font.png`+`font.idx`+`font.json`)缺一件 → 该套不进列表。
4. **热更新 `.new` + `mv` / 覆盖运行中程序的坑**。程序主体在卡上、运行中被覆盖/替换 → 访问旧代码页可能 **SIGBUS**。**修法**:升级用 `nyai install`(同包名 = 更新:旧目录备份 `.bak_<pid>` → 重启后替换 → 数据保留 → 成功自清 `.bak`);别自己 rm/mv 运行中的可执行文件。导出时 `*.pre_*`(热更备份)、`*.bak_*` 会被过滤。
5. **开机早于卡挂载 → amsd 注册表扫空**。`am start/list` 报 `not registered`(agent-250)。**修法**:`am rescan` 手动触发,或等 3s 快重试 / 30s 合并重扫自愈。
6. **`SIGUSR1` 误当返回键**。M2.89 起看门狗只对「**裸沉浸**」App(非 `framework=1`)发 `SIGUSR1`;SDK 框架 App 收到会被 SDK 的 `sigusr1_guard` 护栏记日志+吞掉。**别**为「返回」装 `SIGUSR1` handler;SDK 框架 App 返回走 `on_swipe`。
7. **goodix 幽灵触摸**。真机启动触摸尾部会发一段幽灵触摸(启动后 1.2–2.25s,见 `APPS.md §3.5`)。框架 App 走 `on_tap`/`on_swipe` 语义事件,launcher/SDK 已做 drain + 冷却;裸自绘 App 要自己加启动冷却(如 `COOLDOWN_MS`),否则会把幽灵当下按立即退出。
8. **音频:audiod 不在 / 被抢占**。`meow_audio_open` 返回负值(`MEOW_ERR_NOENT`)→ audiod 不在,**别隐式直开**(`MEOW_AUDIO_DIRECT=1` 仅调试)。被抢占后 `write` 返回负值 → 重新 `open`。长流卡死(~12s 停摆)是早期驱动/ring 问题,已由 audiod 加固(`period=1024×4` + 长流护栏)修掉;App 侧 `write` 阻塞背压,别一 flood 灌。
9. **物理键映射 exit**。旧 hello 曾 `on_key(KEY_POWER)→request_exit()`,违反红线——已要求删除。规范:电力键=息屏/亮屏,播放键=媒体控制,返回=左缘左滑。你的 App 别重蹈。
10. **`meow_ui.a` ABI 变化**。改 SDK(`meow_ui_theme`/`meow_widget` 结构)即 ABI 变化 → **必须同步重编 App**(`MEOWOS_SPEC_REF §3.4`)。SDK 编译期依赖 launcher 头(`-I../meow_launcherd` 引共享 `meow_ipc.h`)是**已知设计**,不是耦合错误——运行时只链 `meow_ui.a` 不会链入 launcher 符号。
---
## 10. 上架检查单
- [ ] `app.json`:`package`(自有前缀)/`exec`(静态路径)/`version`+**`version_code`(单调递增)**/`framework:1`;需大字库加 `fonts`;播放类加 `background_audio`;依赖外部二进制加 `depends`。
- [ ] 二进制是**静态** MIPS32r2 ELF;链 `meow_ui.a` + `libz.a` + `-lpthread`;`-Wall -Wextra` 零警告。
- [ ] 颜色用主题语义色不硬编码;明暗都能看。
- [ ] 控件/区域刷新走 `meow_ui_invalidate`;直绘内容已 `meow_draw_custom_register`。
- [ ] 数据写 `MeowData/<pkg>/`,不写板载/NAND;退出干净 `exit(0)`;根页左滑 `meow_ui_request_exit`。
- [ ] `os.meow.*`(系统签名)vs 第三方(自有前缀,建议也签名防篡改)。打包 `tar czf`(或 `nyai export`,文件名茎=包名)→ `meow_sign sign` → 自测 `verify`/`meow_verify_host`。
- [ ] 真机冒烟:`am start`/`am status`/日志 `MeowOS/logs/<pkg>/`,卡移除/无网/断流降级不崩。