开发文档
Docklet 是一个 Capacitor + React 的 Android 应用坞;目录应用以独立 origin 运行在壳的 iframe 里。本文覆盖技术栈、应用规范、桥接接口和开发注意事项。
技术栈
| 层 | 技术 |
|---|---|
| 壳 UI | React 18 + TypeScript + Vite 5 |
| 原生容器 | Capacitor 6、Android WebView、Java 17 |
| 应用托管 | `AppHostClient` 拦截 `https://<id>.app.local/`,映射到 `filesDir/apps/<id>/` |
| 原生插件 | `AppHostPlugin`(安装/文件/通知/外部打开)、`PhoneRelayPlugin`(对话中转) |
| 状态存储 | 壳:localStorage;应用:独立 origin 的 localStorage / IndexedDB / OPFS;文件:`data/<id>/` |
壳本身不加载远程代码,所有目录应用都来自本地包(zip 导入或随 APK 分发)。
运行模型
- 壳页面地址是
https://localhost/,由 Capacitor 的本地 WebView 提供。 - 每个应用有独立 origin:
https://<id>.app.local/,由原生shouldInterceptRequest拦截。 - 应用放在 iframe 中,
sandbox="allow-scripts allow-same-origin allow-forms"。 - 独立 origin 的好处:localStorage / IndexedDB / OPFS 天然隔离,配额独立,卸载可整块清理。
- 壳负责返回栈、键盘 insets、安全区和外部链接;应用不直接改 WebView。
目录结构
apps/<id>/ 源码(只读),导入/更新时整体替换
game.json | app.json 应用清单
index.html | app.js 入口
android/.../assets/apps/ 构建时同步进 APK
data/<id>/ 应用数据(可写),更新保留、卸载删除
apps/<id>/.docklet/state.json 壳写的安装记录(应用不要依赖)
数据目录只能通过 fs 能力访问,路径必须落在自己的 data/<id>/ 下。
应用清单
包根目录必须有 game.json 或 app.json,缺失会被拒绝安装。
{
"spec": 1,
"id": "inventory",
"name": "库存",
"version": "1.0.0",
"entry": "index.html",
"icon": "📦",
"description": "轻量的物品与数量记录",
"author": "Docklet 官方",
"updatedAt": "2026-11-06",
"capabilities": ["fs", "notify"],
"permissions": [
{ "name": "android.permission.POST_NOTIFICATIONS", "reason": "接收库存提醒" }
]
}
| 字段 | 必填 | 说明 |
|---|---|---|
id | 是 | ^[a-z][a-z0-9-]{1,31}$,导入后不可变,决定 origin |
name | 是 | 显示名 |
version | 是 | 语义化版本,用于「可更新」判断 |
entry | 是 | 包内相对路径,必须是已存在的 .html / .js |
icon | 否 | emoji 或图片路径 |
description | 否 | 应用列表/长按菜单显示,单行省略 |
author / updatedAt | 否 | 详情页展示 |
capabilities | 否 | 只能使用下方枚举,未知值拒绝导入 |
permissions | 否 | 每条必须有 reason,否则拒绝导入 |
ZIP_UNSAFE、ENTRY_MISSING、MANIFEST_INVALID、TOO_LARGE、CAPABILITY_UNKNOWN、PERMISSION_NO_REASON。能力与权限
能力(capabilities)
能力是壳注入的接口,不声明就没有;由壳在桥接层强制校验。
| 能力 | 提供 |
|---|---|
fs | 读写自己的 data/<id>/ |
openExternal | 用系统浏览器打开 http/https 链接 |
clipboard | 写剪贴板 |
share | 系统分享 |
background | 后台监听/提醒 |
download | 把文件存到系统「下载」目录 |
notify | 发系统通知 |
storage | 应用私有键值存储(app.<id>.*) |
权限(permissions)
Android 系统权限,声明后只在应用真正使用时申请,并且必须写清原因。安装页与详情页会把「声明」和「已生效」分开显示。
桥接接口
HTML 应用
应用通过 window.docklet 调用壳能力;未声明的能力会返回 CAPABILITY_DENIED。
| 方法 | 说明 |
|---|---|
docklet.fs.read/write/list/delete | 只能操作自己的 data 目录 |
docklet.openExternal(url) | 只接受 http/https |
docklet.download(url, filename?) | 存到系统下载目录 |
docklet.clipboard.write(text) | 写剪贴板 |
docklet.share(...) | 系统分享 |
docklet.notify(...) | 发通知(需通知权限) |
docklet.shell.exit() | 关闭应用,回到来源页 |
docklet.ui.insets() | { top, bottom, ime },CSS px;键盘打开时 bottom 为 0 |
docklet.ui.onInsetsChange(fn) | 订阅 insets 变化,返回取消订阅 |
module 应用
入口是 .js 时按 module 加载,导出 mount(root, api),返回可选的 unmount。常用 api:version、manifest、storage.get/set/remove、audio.tone/noise/setMuted、score.get/add/best/finish/reset、shell.exit/restart、watch.start/stop/status/test、on('pause'|'resume')、openExternal、download、clipboard.write、ui.insets/onInsetsChange。
后台监听限制:最多 4 个应用同时监听,间隔最小 2 秒,文本最长 80 字,endpoint 只接受 http/https;策略存原生,应用卸载后清掉。
网络与安全
- 应用代码必须随包分发,入口是本地文件;不要在运行时从网络加载主逻辑。
- iframe sandbox 禁止顶层导航和弹窗;打开外链必须走
openExternal。 - 能力按清单注入,壳不提供任意原生调用;未声明的接口直接拒绝。
- 应用的存储只属于它自己的 origin;壳不读应用 localStorage。
- 应用卸载会删除代码、
data/<id>/和该 origin 的 WebView 存储。
本地开发
npm install
npx vite --port 5173 # 浏览器预览壳
npx cap sync android # 同步 Web 资源到 Android
node scripts/sync-apps.mjs # 同步 apps/ 到原生 assets
powershell -ExecutionPolicy Bypass -File .\\build-apk.ps1
调试:Android WebView 可通过 chrome://inspect 连接(debug 包),原生日志用 adb logcat。目录应用在浏览器里可直接访问 /apps/<id>/,但 origin 与真机不同,涉及存储/能力的行为以真机为准。
构建与版本
- 版本号规则:首个公开版
1.0.1;小改1.0.2;大改1.1.1。 versionCode每次发布必须递增,否则不能覆盖安装。- 构建号(
YYYYMMDD-HHmm)只用于内部识别,不混进版本名。 - 签名文件
android/*.jks与keystore.properties不进仓库;换签名后旧包不能覆盖安装。
开发注意事项
键盘
WebView 是覆盖式键盘,窗口不会缩小。不要自己改 viewport 或监听 resize 猜高度,用 docklet.ui.insets() / onInsetsChange;壳的全屏层统一用 --kb-inset 让位。
返回键
壳只有一套返回栈;应用内的面板/抽屉要自己先关,再调用 shell.exit(),不要用 history.back() 造成壳与页面状态不一致。
密码类输入
不要用 type="password":部分 ROM 会强制切安全键盘,无法截图、部分机型无法输字母。用 type="text" + -webkit-text-security: disc。
大字号
壳只放大自己界面。应用内请用相对单位(rem / em / %),并给容器留可伸缩空间,不要写死像素行高。
存储与数据
- localStorage 有配额,别存大文件;大文件用
fs写data/<id>/。 - 应用更新不会动 data 目录;卸载会全部删除,需要保留的数据要让用户先导出。
- 不要把数据写进代码目录,更新时会被整体替换。
界面
- iframe sandbox 下
alert/confirm不保证可用,用应用内 UI。 - 避免依赖
window.top/ 父页面 DOM;只能通过桥接通信。 - 首屏保持小:图片按需加载,避免大体积字体和未压缩资源。
发布流程
- 更新清单
version,跑build-apk.ps1生成 APK。 - APK 上传 GitHub Releases,tag 用版本号(如
v1.0.1)。 - 官网
site/由 Cloudflare Pages 托管,输出目录填site。 - 应用列表页展示当前内置应用;新应用先随壳分发,后续再接在线安装。