01 / 从工程到模板
- 建立模板目录并放入工程骨架。
- 在
template.json中声明类型、分类、预览图和变量。 - 把需要替换的值写成
{{variableName}}。 - 可选:在
template-hooks/携带前置或后置脚本。 - 将目录压缩为 ZIP,在“工坊 → 模板管理”导入。
- 验证后直接导出 ZIP,与其他用户分享。
最快的开始方式是在模板管理页导出一个内置模板。修改 template.json 的 id,并移除系统保留的 builtin. 前缀后再导入。
02 / 模板包目录结构
模板使用普通 ZIP。ZIP 根目录可以直接放置清单,也可以只包含一个模板文件夹。
my-android-template/
├── template.json
├── preview.png # 可选,固定 270×270
├── template-hooks/ # 可选
│ ├── before-create.sh
│ └── after-create.sh
└── app/
├── build.gradle.kts
└── src/main/java/TAIXU_PACKAGE_PATH/
└── MainActivity.kt.template
- 最多 5000 个 ZIP 条目。
- 单文件最大 16 MiB。
- 总解压体积最大 128 MiB。
- 绝对路径、Windows 盘符和包含
..的越界路径会被拒绝。
03 / 编写 template.json
{
"schemaVersion": 1,
"id": "example.android-basic",
"name": "Basic Android",
"version": "1.0.0",
"description": "A reusable Android starter",
"projectType": "ANDROID",
"category": {
"id": "starter",
"name": "Starter",
"sortOrder": 0
},
"previewImage": "preview.png",
"variables": []
}
| 字段 | 当前约束 |
|---|---|
schemaVersion | 当前固定为 1。 |
id | 小写字母和数字,可用点、下划线、短横线分隔;builtin. 为保留前缀。 |
projectType | ANDROID、FLUTTER 或 GENERAL。 |
category | 模板选择页的分组;sortOrder 越小越靠前。 |
previewImage | 可选的单张 1:1、270×270 预览图。 |
variables | 声明动态表单和所有模板变量。 |
hooks | 可选的前置、后置构造脚本。 |
validation | 模板自带的生成结果校验规则。 |
清单采用严格解析,未知字段不会被忽略。机器可读的 JSON Schema 可从项目仓库直接获取。
04 / 让清单渲染动态 UI
"variables": [
{
"name": "packageName",
"label": "Package name",
"prompt": true,
"inputType": "TEXT",
"placeholder": "com.example.app",
"validationRegex": "^[a-zA-Z_][\\w]*(\\.[a-zA-Z_][\\w]*)+$"
},
{
"name": "uiStyle",
"label": "UI style",
"prompt": true,
"inputType": "SELECT",
"defaultValue": "compose",
"options": [
{ "value": "compose", "label": "Jetpack Compose" },
{ "value": "views", "label": "Android Views" }
]
}
]
prompt: true 会在创建工程的最后一步自动渲染控件。支持 TEXT、MULTILINE、NUMBER、BOOLEAN、SELECT 和 SECRET。
- 变量名必须匹配
^[A-Za-z][A-Za-z0-9_]*$,且不能重复。 - 隐藏必填变量必须提供默认值,系统派生变量除外。
SELECT必须有不重复的选项;可选字段允许留空。- 项目名称与创建路径由工坊统一填写,不应伪装成动态字段。
系统提供 projectName、appName、projectPath;如果模板把 appName 设为动态字段,则优先使用用户输入。只有模板声明 packageName 或 packagePath 时,才会启用 Java 包名规则。
模板可将 projectName 声明为 prompt: false 的固定变量,并用 validationRegex 约束统一项目名称输入框。校验失败时工坊显示该变量的 description 并禁止创建,不会静默改写用户输入。
05 / 替换文件内容和路径
文本内容与相对路径都能使用变量:
modules/{{moduleName}}/config.json.template
{
"name": "{{projectName}}",
"mode": "{{buildMode}}"
}
生成时会替换变量并移除末尾的 .template。任意需要替换的文本都建议使用该后缀;Java、Kotlin、Dart、JSON、YAML、TOML、Shell、Python、JavaScript、C/C++ 等常见文本格式也会自动识别。
包目录使用 TAIXU_PACKAGE_PATH,创建时会替换为包名对应的目录层级。不要使用以下划线开头的占位目录,否则可能被 Android AAPT 忽略。变量生成的路径会再次规范化,越界路径会被拒绝。若文件中残留未声明的 {{unknownVariable}},创建会直接失败。
06 / 统一方形预览图
模板只声明一张预览图,不再区分手机、平板或横竖屏。比例固定为 1:1,尺寸必须为 270×270 px;支持 PNG、JPEG、WebP 和 GIF,单图不超过 4 MiB。
把 Logo、标题和关键内容留在中央安全区,并避开四周约 8%,保证不同尺寸的模板卡片都能清晰展示。
07 / 携带可审查的构造脚本
"hooks": {
"beforeCreate": "template-hooks/before-create.sh",
"afterCreate": "template-hooks/after-create.sh"
}
脚本不会在导入、查看或导出时执行。创建工程时,太墟会显示脚本内容,只有用户明确勾选授权后才执行。
- 脚本必须位于
template-hooks/,单个最大 1 MiB。 - 在 Linux 沙箱的新工程目录中运行,单阶段最长 60 秒。
beforeCreate在复制模板文件前执行,afterCreate在物化后执行。- 工程路径为
TAIXU_PROJECT_DIR。 - 变量以
TAIXU_VAR_<大写变量名>传入。 - 非零退出会终止创建,并把错误输出返回界面。
#!/bin/sh
set -eu
mkdir -p generated
printf '%s\n' "$TAIXU_VAR_PROJECTNAME" > generated/project-name.txt
08 / 模板自带结果校验
"validation": {
"requiredFiles": ["app/src/main/java/TAIXU_PACKAGE_PATH/MainHook.kt"],
"forbiddenFiles": ["app/src/main/java/TAIXU_PACKAGE_PATH/LegacyHook.kt"],
"contentRules": [
{
"path": "app/src/main/assets/xposed_init",
"equals": "{{packageName}}.MainHook"
}
]
}
requiredFiles 声明必需文件,forbiddenFiles 声明禁止产物,contentRules 支持 equals、contains 和 excludes。路径与内容均可使用模板变量。
校验在 afterCreate 完成后执行,规则属于模板本身,与内置 ID 无关;模板改名、导出和重新导入后行为保持一致。
09 / 打包、导入与分享
Windows PowerShell
Compress-Archive `
-Path .\my-android-template\* `
-DestinationPath .\my-android-template.zip -Force
Linux / macOS
cd my-android-template
zip -r ../my-android-template.zip .
在 Android 应用中打开“工坊 → 更多 → 模板管理 → 导入 ZIP”。导入成功后,模板会自动进入对应项目类型和分类;不需要修改应用代码或注册表。
模板管理页还可以导出模板。用户模板可删除,内置模板受到保护。
10 / 一个完整的无包名模板
GENERAL 模板不依赖 Android 包名:
{
"schemaVersion": 1,
"id": "example.shell-project",
"name": "Shell Project",
"version": "1.0.0",
"projectType": "GENERAL",
"category": { "id": "script", "name": "Scripts", "sortOrder": 0 },
"variables": [
{
"name": "greeting",
"label": "Greeting",
"prompt": true,
"inputType": "TEXT",
"defaultValue": "Hello"
}
]
}
在 README.md.template 中写入 # {{projectName}}\n{{greeting}} 即可。工程名称与路径仍由所有模板共用的创建页面提供。
11 / 发布前检查
- 清单可通过 v1 JSON Schema,且没有未知字段。
- ID 未使用
builtin.,变量名与分类 ID 合法且不重复。 - 项目名称和路径没有被声明成动态字段。
- 所有文本占位符均已声明,路径变量不会产生越界。
- 单张预览图符合 1:1、270×270、格式和体积限制。
- 脚本位于指定目录,不包含密钥,并能在 60 秒内结束。
- 在全新目录中完成导入、表单填写、创建、导出和重新导入。
- GENERAL 模板已确认不会被错误要求填写 Java 包名。
