原生应用内模板语法
原生应用内消息由 SDK 直接渲染。不涉及 WebView。ZIP 资源不包含 index.html 页面,而是携带一个 native-config.json 文件,该文件将消息描述为结构化数据(布局类型、文本、颜色、图像、按钮)。SDK 读取此文件并绘制匹配的原生视图,与嵌入式网页相比,这能提供更流畅的动画和更好的性能。
本指南记录了 native-config.json 的模式:字段、类型以及每种显示类型的示例。关于经典的基于 HTML 的格式,请参阅富媒体模板语法。
先决条件
Anchor link to原生应用内消息需要:
- iOS: SDK 7.2.0 或更高版本(横幅、轮播和底部弹窗需要 7.2.1+)
- Android: SDK 6.10.0 或更高版本(横幅、轮播和底部弹窗需要 6.10.1+)
并非所有显示类型都已在两个平台上可用。在依赖特定格式之前,请检查平台支持。
平台支持
Anchor link to| 显示类型 | iOS | Android |
|---|---|---|
| modal | ✅ 7.2.0+ | ✅ 6.10.0+ |
| fullscreen | ✅ 7.2.0+ | ✅ 6.10.0+ |
| stories | ✅ 7.2.0+ | ✅ 6.10.0+ |
| banner | ✅ 7.2.1+ | ✅ 6.10.1+ |
| carousel | ✅ 7.2.1+ | ✅ 6.10.1+ |
| sheet | ✅ 7.2.1+ | ✅ 6.10.1+ |
| video | ✅ | 尚不可用 |
| pip | ✅ | 尚不可用 |
| scratchcard | ✅ | 尚不可用 |
| spinwheel | ✅ | 尚不可用 |
模板结构
Anchor link to原生应用内模板是一个 ZIP 压缩包,与常规的富媒体模板相同,只是根目录包含一个 native-config.json 文件,而不是 index.html:
<template>.zip├── native-config.json ← 必需,布局和内容├── pushwoosh.json ← 可选,本地化(见下文)native-config.json 中引用的图像和视频(pip/video 上的 image、poster、fallback、url)必须是绝对的 HTTPS URL。SDK 通过网络加载它们,不从压缩包中读取本地文件。
配置本身是一个单一的 JSON 对象:
{ "displayType": "<type>", "<type>": { /* 此类型的内容块 */ } }displayType 选择以下十种格式之一。匹配键下的对象持有该格式的内容。如果配置的 displayType 未知、缺少内容块或必需的列表为空(carousel/stories 的 items,spinwheel 的 segments),则该配置无效。SDK 会跳过显示它,而不是渲染一个损坏的布局。
投放设置(开始/结束日期和频率上限)不属于 native-config.json 的一部分。它们的配置方式与任何其他应用内消息相同,在营销活动的显示设置步骤中进行配置。
频率上限还需要在 SDK 端明确选择加入才能对原生应用内消息生效。请参阅 SDK 集成。
每个颜色值都是一个 CSS 十六进制字符串:#RGB、#RGBA、#RRGGBB 或 #RRGGBBAA。所有四种形式都必须以 # 开头。
共享构建块
Anchor link to这些较小的对象在多种显示类型中被重用。
Text
Anchor link to| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
text | string | 是 | 文本内容 |
color | string | 是 | 文本颜色 |
{ "text": "Spin for a garage perk", "color": "#FFFFFFFF" }Border
Anchor link to| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
color | string | 是 | 边框颜色 |
radius | number | 是 | 圆角半径,单位为点 |
{ "color": "#0E72E5FF", "radius": 12 }Cover
Anchor link to背景颜色上的可选图像。由 fullscreen 和 scratchcard 使用。
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
image | string | 否 | 封面图片 URL |
background | string | 是 | 在图片下方(或替代图片)显示的背景颜色 |
{ "image": "https://example.com/cover.jpg", "background": "#1A1A1EFF" }Action
Anchor link to一个基于 type 的可辨识联合体:
| 变体 | 字段 | 描述 |
|---|---|---|
{ "type": "close" } | 无 | 关闭应用内消息 |
{ "type": "url", "url": string } | url 必需 | 打开一个 URL 或深层链接 |
{ "type": "url", "url": "pushwoosh://sale" }Button
Anchor link to| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
text | Text | 是 | 按钮标签 |
background | string | 是 | 按钮填充颜色 |
border | Border | 是 | 按钮边框 |
action | Action | 是 | 点击时触发的动作 |
spinButton (spinwheel) 和 revealButton (scratchcard) 使用相同的形状,但没有 action。它们的行为(旋转轮盘,揭开卡片)是内置的。
{ "text": { "text": "Book a test drive", "color": "#FFFFFFFF" }, "background": "#0E72E5FF", "border": { "color": "#0E72E5FF", "radius": 12 }, "action": { "type": "url", "url": "pushwoosh://sale" }}Reward
Anchor link to由 scratchcard 和 spinwheel 显示的奖励面板。一个有效的奖励必须有 title 或 code。
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
title | Text | 否 | 奖励标题 |
message | Text | 否 | 奖励描述 |
code | string | 否 | 优惠码,会带有一个复制按钮进行渲染 |
button | Button | 否 | 带有自身动作的确认按钮 |
{ "title": { "text": "20% off detailing", "color": "#111111FF" }, "message": { "text": "Valid for any full-detail booking this month.", "color": "#555555FF" }, "code": "APEX20", "button": { "text": { "text": "Book detailing", "color": "#FFFFFFFF" }, "background": "#B3227CFF", "border": { "color": "#B3227CFF", "radius": 12 }, "action": { "type": "url", "url": "pushwoosh://detailing" } }}显示类型
Anchor link tobanner
Anchor link to一个紧凑的条,停靠在屏幕的顶部或底部边缘。
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
showClose | boolean | 是 | 显示一个关闭 (✕) 按钮 |
position | top | bottom | 是 | 屏幕边缘 |
background | string | 是 | 条的背景颜色 |
image | string | 否 | 左侧的缩略图 |
title | Text | 否 | 单行标题,用省略号截断 |
message | Text | 否 | 正文文本,最多 2 行 |
action | Action | 是 | 当条本身被点击时触发 |
autoDismiss | number | 否 | 这么多秒后自动关闭。省略则保持显示直到关闭 |
{ "displayType": "banner", "banner": { "showClose": true, "position": "bottom", "background": "#4B5057FF", "image": "https://example.com/thumb.jpg", "title": { "text": "Alpine A110 just dropped", "color": "#FFFFFFFF" }, "message": { "text": "The featherweight icon — tap to see the build", "color": "#FFFFFFFF" }, "action": { "type": "url", "url": "pushwoosh://product/x6f" }, "autoDismiss": 6 }}carousel
Anchor link to一个全屏、可滑动的卡片集,带有页面指示点。
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
showClose | boolean | 是 | 显示一个关闭 (✕) 按钮 |
items | Item[] | 是 | 卡片(至少 1 个) |
轮播项:
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
title | Text | 否 | 卡片标题 |
message | Text | 否 | 卡片副标题 |
image | string | 否 | 卡片图片 |
action | Action | 否 | 当卡片被点击时触发 |
{ "displayType": "carousel", "carousel": { "showClose": true, "items": [ { "image": "https://example.com/card-1.jpg", "title": { "text": "AMG GT R", "color": "#FFFFFFFF" }, "message": { "text": "585 hp biturbo V8 — just landed", "color": "#FFFFFFFF" }, "action": { "type": "url", "url": "pushwoosh://product/n6fx" } }, { "image": "https://example.com/card-2.jpg", "title": { "text": "Alpine A110", "color": "#FFFFFFFF" }, "message": { "text": "Featherweight icon — limited allocation", "color": "#FFFFFFFF" }, "action": { "type": "url", "url": "pushwoosh://product/x6f" } } ] }}fullscreen
Anchor link to一个边缘到边缘的封面图片,上面有文本和按钮。
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
showClose | boolean | 是 | 显示一个关闭 (✕) 按钮 |
cover | Cover | 是 | 背景图片和颜色 |
title | Text | 否 | 标题 |
message | Text | 否 | 正文文本 |
buttons | Button[] | 是 | 底部的按钮(可以为空) |
{ "displayType": "fullscreen", "fullscreen": { "showClose": true, "cover": { "image": "https://example.com/hero.jpg", "background": "#1A1A1EFF" }, "title": { "text": "Pure Maranello", "color": "#FFFFFFFF" }, "message": { "text": "The prancing horse, reimagined.", "color": "#EBEBEBFF" }, "buttons": [ { "text": { "text": "Reserve now", "color": "#FFFFFFFF" }, "background": "#0E72E5FF", "border": { "color": "#0E72E5FF", "radius": 8 }, "action": { "type": "url", "url": "pushwoosh://sale" } }, { "text": { "text": "Not now", "color": "#FFFFFFFF" }, "background": "#00000000", "border": { "color": "#FFFFFF99", "radius": 8 }, "action": { "type": "close" } } ] }}modal
Anchor link to一个居中的卡片。
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
showClose | boolean | 是 | 显示一个关闭 (✕) 按钮 |
dimBackground | boolean | 是 | 使卡片后面的屏幕变暗 |
background | string | 是 | 卡片背景颜色 |
image | string | 否 | 封面图片 |
title | Text | 否 | 标题 |
message | Text | 否 | 正文文本 |
buttons | Button[] | 是 | 文本下方的按钮(可以为空) |
{ "displayType": "modal", "modal": { "showClose": true, "dimBackground": true, "background": "#FFFFFFFF", "image": "https://example.com/cover.jpg", "title": { "text": "The GT R has landed", "color": "#4B5057FF" }, "message": { "text": "585 hp — now in the showroom.", "color": "#4B5057FF" }, "buttons": [ { "text": { "text": "Book a test drive", "color": "#FFFFFFFF" }, "background": "#0E72E5FF", "border": { "color": "#0E72E5FF", "radius": 12 }, "action": { "type": "url", "url": "pushwoosh://sale" } }, { "text": { "text": "Not now", "color": "#4B5057FF" }, "background": "#FFFFFFFF", "border": { "color": "#4B5057FF", "radius": 12 }, "action": { "type": "close" } } ] }}一个浮动的画中画视频窗口,停靠在屏幕一角。
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
showClose | boolean | 是 | 显示一个关闭 (✕) 按钮 |
position | bottom-right | bottom-left | top-right | top-left | 是 | 屏幕角落 |
loop | boolean | 是 | 循环播放 |
muted | boolean | 是 | 开始时静音 |
url | string | 是 | 视频 URL |
poster | string | 否 | 播放开始前显示的海报 |
fallback | string | 否 | 视频播放失败时显示的图片 |
width | number | 是 | 窗口宽度占屏幕宽度的百分比,限制在 15–70 之间 |
aspectRatio | number | 是 | 窗口高宽比 |
borderRadius | number | 否 | 窗口圆角半径,单位为点 |
action | Action | 否 | 当窗口本身被点击时触发 |
pip 上没有可配置的按钮。窗口控件(展开到全屏、静音、关闭)由系统提供。
{ "displayType": "pip", "pip": { "showClose": true, "position": "bottom-right", "loop": true, "muted": true, "url": "https://example.com/teaser.mp4", "poster": "https://example.com/poster.jpg", "width": 40, "aspectRatio": 0.5625, "action": { "type": "url", "url": "pushwoosh://product/x6f" } }}scratchcard
Anchor link to一张卡片,奖励隐藏在可刮开的箔层下。
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
showClose | boolean | 是 | 显示一个关闭 (✕) 按钮 |
background | string | string[] | 是 | 卡片背景颜色,或渐变色标 |
revealThreshold | number | 是 | 必须刮掉的箔层比例 (0–1),之后奖励才会显示 |
cover | Cover | 是 | 箔层。如果没有 image,则在背景颜色上显示“在此刮开”的提示 |
revealButton | Button (无 action) | 否 | “立即揭晓”按钮 |
title | Text | 否 | 标题 |
message | Text | 否 | 正文文本 |
reward | Reward | 是 | 隐藏在箔层下的奖品 |
{ "displayType": "scratchcard", "scratchcard": { "showClose": true, "background": ["#3A1C71FF", "#B3227CFF", "#E0503AFF"], "revealThreshold": 0.55, "cover": { "background": "#C9CDD6FF" }, "revealButton": { "text": { "text": "Reveal without scratching", "color": "#3A1C71FF" }, "background": "#F2DFF5FF", "border": { "color": "#F2DFF5FF", "radius": 10 } }, "title": { "text": "Your loyalty reward", "color": "#FFFFFFFF" }, "message": { "text": "Scratch the foil to reveal this week's garage perk.", "color": "#F2DFF5FF" }, "reward": { "title": { "text": "20% off detailing", "color": "#111111FF" }, "message": { "text": "Valid for any full-detail booking this month.", "color": "#555555FF" }, "code": "APEX20", "button": { "text": { "text": "Book detailing", "color": "#FFFFFFFF" }, "background": "#B3227CFF", "border": { "color": "#B3227CFF", "radius": 12 }, "action": { "type": "url", "url": "pushwoosh://detailing" } } } }}sheet
Anchor link to一张固定在底部边缘的卡片,带有一个拖动手柄。
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
showClose | boolean | 是 | 显示一个关闭 (✕) 按钮 |
dimBackground | boolean | 是 | 使底部弹窗后面的屏幕变暗 |
background | string | 是 | 底部弹窗背景颜色 |
image | string | 否 | 封面图片 |
title | Text | 否 | 标题 |
message | Text | 否 | 正文文本 |
buttons | Button[] | 是 | 文本下方的按钮(可以为空) |
{ "displayType": "sheet", "sheet": { "showClose": true, "dimBackground": true, "background": "#FFFFFFFF", "image": "https://example.com/cover.jpg", "title": { "text": "Your quote is ready", "color": "#000000FF" }, "message": { "text": "Guaranteed buyout for your A110: $68,500.", "color": "#000000FF" }, "buttons": [ { "text": { "text": "Get guaranteed quote", "color": "#FFFFFFFF" }, "background": "#0E72E5FF", "border": { "color": "#0E72E5FF", "radius": 12 }, "action": { "type": "url", "url": "pushwoosh://sale" } } ] }}spinwheel
Anchor link to一个带有加权扇区和中心枢纽按钮的幸运转盘。
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
showClose | boolean | 是 | 显示一个关闭 (✕) 按钮 |
background | string | string[] | 是 | 卡片背景颜色,或渐变色标 |
winIndex | number | 是 | 获胜扇区的索引(从 0 开始) |
spinButton | Button (无 action) | 是 | 中心枢纽按钮 |
title | Text | 否 | 标题 |
message | Text | 否 | 正文文本 |
reward | Reward | 是 | 获胜旋转的奖励(作为没有自身奖励的扇区的备用) |
loseTitle | Text | 否 | 失败时显示的标题 |
segments | Segment[] | 是 | 轮盘扇区(SDK 期望 2–12 个) |
扇区:
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
message | Text | 是 | 扇区标签 |
color | string | 否 | 扇区颜色。省略则使用围绕轮盘应用的备用调色板 |
weight | number | 是 | 相对扇区大小 |
reward | Reward | 否 | 特定扇区的奖励 |
{ "displayType": "spinwheel", "spinwheel": { "showClose": true, "background": ["#1B1B46FF", "#5B2B8FFF", "#B0338AFF"], "winIndex": 1, "spinButton": { "text": { "text": "SPIN", "color": "#1B1B46FF" }, "background": "#F2C94CFF", "border": { "color": "#D9A02BFF", "radius": 36 } }, "title": { "text": "Spin for a garage perk", "color": "#FFFFFFFF" }, "message": { "text": "One spin — every slice wins this week.", "color": "#E3D9F2FF" }, "reward": { "title": { "text": "You won a garage perk!", "color": "#FFFFFFFF" }, "code": "APEXPERK", "button": { "text": { "text": "Claim", "color": "#FFFFFFFF" }, "background": "#5B2B8FFF", "border": { "color": "#5B2B8FFF", "radius": 12 }, "action": { "type": "close" } } }, "segments": [ { "message": { "text": "5% off", "color": "#FFFFFFFF" }, "color": "#5856D6FF", "weight": 1 }, { "message": { "text": "20% off", "color": "#FFFFFFFF" }, "color": "#30B0C7FF", "weight": 1, "reward": { "title": { "text": "20% off your next service", "color": "#FFFFFFFF" }, "code": "SPIN20", "button": { "text": { "text": "Claim service deal", "color": "#FFFFFFFF" }, "background": "#30B0C7FF", "border": { "color": "#30B0C7FF", "radius": 12 }, "action": { "type": "url", "url": "pushwoosh://service" } } } }, { "message": { "text": "Free wash", "color": "#FFFFFFFF" }, "color": "#FF2D55FF", "weight": 1 } ] }}stories
Anchor link to全屏幻灯片,顶部有进度条,类似于社交媒体故事。
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
showClose | boolean | 是 | 显示一个关闭 (✕) 按钮 |
loop | boolean | 是 | 在最后一张幻灯片后从第一张重新开始 |
items | Item[] | 是 | 幻灯片(至少 1 张) |
故事项:
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
title | Text | 否 | 标题 |
message | Text | 否 | 副标题 |
image | string | 否 | 幻灯片背景图片 |
buttons | Button[] | 是 | 底部的 CTA 按钮(可以为空) |
duration | number | 是 | 幻灯片持续时间,单位为秒 |
{ "displayType": "stories", "stories": { "showClose": true, "loop": false, "items": [ { "image": "https://example.com/slide-1.jpg", "title": { "text": "AMG GT R", "color": "#FFFFFFFF" }, "message": { "text": "The Green Hell special", "color": "#FFFFFFFF" }, "buttons": [ { "text": { "text": "Configure yours", "color": "#FFFFFFFF" }, "background": "#0F0F0FFF", "border": { "color": "#0F0F0FFF", "radius": 26 }, "action": { "type": "url", "url": "pushwoosh://product/n6fx" } } ], "duration": 4 }, { "image": "https://example.com/slide-2.jpg", "title": { "text": "Alpine A110", "color": "#FFFFFFFF" }, "message": { "text": "The featherweight legend, reborn", "color": "#FFFFFFFF" }, "buttons": [], "duration": 4 } ] }}video
Anchor link to全屏视频,上面有文本和按钮。
| 字段 | 类型 | 必需 | 描述 |
|---|---|---|---|
showClose | boolean | 是 | 显示一个关闭 (✕) 按钮 |
loop | boolean | 是 | 循环播放 |
muted | boolean | 是 | 开始时静音 |
url | string | 是 | 视频 URL (HLS 或 MP4) |
poster | string | 否 | 播放开始前显示的海报 |
fallback | string | 否 | 视频播放失败时显示的图片 |
title | Text | 否 | 标题 |
message | Text | 否 | 正文文本 |
buttons | Button[] | 是 | 底部的 CTA 按钮(可以为空) |
{ "displayType": "video", "video": { "showClose": true, "loop": true, "muted": true, "url": "https://example.com/reveal.mp4", "poster": "https://example.com/poster.jpg", "title": { "text": "The reveal", "color": "#FFFFFFFF" }, "message": { "text": "Watch it move before anyone else.", "color": "#EBEBEBFF" }, "buttons": [ { "text": { "text": "Shop the lineup", "color": "#FFFFFFFF" }, "background": "#0E72E5FF", "border": { "color": "#0E72E5FF", "radius": 14 }, "action": { "type": "url", "url": "pushwoosh://sale" } } ] }}原生应用内消息重用与 HTML 富媒体完全相同的本地化机制:native-config.json 中的字符串值可以携带 {{key|type|default}} 占位符,翻译内容存放在旁边的 pushwoosh.json 文件中,格式与添加 pushwoosh.json 中描述的相同。占位符可以出现在任何字符串字段中,任何深度(标题、按钮标签、图片 URL、动作 URL)。
SDK 集成
Anchor link to一旦您将原生应用内 SDK 模块添加到您的应用中,消息就会自动显示。无需额外代码即可显示由推送、Customer Journey、postEvent 或收件箱触发的消息。
SDK 还公开了一个小型的 API 用于手动控制:
- iOS:
Pushwoosh.inApp(模块PushwooshInApp) - Android:
PushwooshInAppUi(模块pushwoosh-inapp-ui)
| 功能 | iOS | Android |
|---|---|---|
| 直接显示配置(测试/手动使用) | Pushwoosh.inApp.present(config) | PushwooshInAppUi.present(configJson) |
| 观察生命周期和点击 | delegate (PWInAppMessageDelegate) | delegate (InAppMessageDelegate) |
| 检查屏幕上是否有内容 | isPresenting | isPresenting |
| 关闭当前显示的任何内容 | dismiss() | dismiss() |
| 暂停/恢复显示 | isPaused | isPaused |
强制执行 maxDisplays / cooldown 上限 | setFrequencyCapEnabled(_:) | setFrequencyCapEnabled(...) |
委托回调(都在主线程上触发):shouldDisplay(返回 false 以在消息显示前抑制它,例如在结账屏幕上)、willPresent、didPresent、didClose 和 clickedAction(当用户点击 url 动作时,在 URL 打开前触发)。
iOS 还会为游戏化的 scratchcard 和 spinwheel 模板报告 rewardRevealed 和 rewardClaimed。
// iOSPushwoosh.inApp.delegate = selfPushwoosh.inApp.setFrequencyCapEnabled(true)// AndroidPushwooshInAppUi.delegate = thisPushwooshInAppUi.setFrequencyCapEnabled(true)