主题
Skip to content
Cocos App 上报
埋点接入
统计模块用于上报玩法、模式、关卡、道具、广告等业务数据。本页适用于 Android / iOS App SDK,覆盖 Unity 与 Cocos Creator / Laya 两种接入方式。
SDK 已自动处理启动、公共时长、广告生命周期、崩溃等基础事件;业务层只接入与游戏玩法强相关的事件。首次接入建议先完成“玩法 → 模式 → 关卡”的最小闭环,再按埋点表增加扩展事件。
接入前提
埋点只能在 SDK 初始化完成后调用。尚未完成 SDK 接入时,请先阅读 快速接入。
1. 统一概念与规则
1.1 数据层级
| 层级 | 事件字段 | 示例 | 说明 |
|---|---|---|---|
| 玩法 | game_name | KXPYP | 最大业务单元,例如主玩法、PVP |
| 模式 | mode_name | normal | 玩法下的模式,例如闯关、每日挑战 |
| 关卡 | mode_level | 50 | 模式内的一局或一关 |
没有模式时,与对接人确认后统一使用 wujin;没有关卡时,mode_level 统一传 0。没有分数时不要伪造数据:Unity 保持可空字段为 null,Cocos / Laya 使用接口默认值 -1。
1.2 三个核心字段怎么传
| 字段 | Unity SDK | Cocos Creator / Laya SDK |
|---|---|---|
game_name | 在 Assets/Resources/ZMYSDKAPPGlobalConfig.json 中配置 gameName,SDK 初始化时自动注入 | 调用 EnterGame(gameName) |
mode_name | 调用 StartGameModel(modeName) | 调用 EnterMode(modeName) |
mode_level | 给 game_start.mode_level 赋值,再调用 NewReportGameStart | 调用 StartGame(level) |
首次传入时机:
game_name:Unity 在 SDK 初始化前配置;Cocos Creator / Laya 在玩家真正进入玩法时设置。必须早于模式和关卡事件。mode_name:玩家进入或切换模式时传入,必须早于关卡开始。mode_level:每一局、每一关开始时传入;重新开始也要再次设置。
这三个值不是每条事件都重新传
先按玩法、模式、关卡顺序建立 SDK 当前状态,后续标准事件会继承当前值。Unity 的手动关卡事件仍建议显式填写 mode_level;Cocos / Laya 不要在 extra 中覆盖这三个字段。
字段更新时机:
- 同一模式进入下一关:只更新
mode_level。 - 同一关重新开始:重新上报关卡开始,仍传当前关卡编号。
- 切换模式:先更新
mode_name,再开始新关卡。 - 返回首页或退出模式:结束当前关卡状态,并调用对应的模式退出接口。
1.3 调用原则
- 事件名、玩法名、模式名、物品 ID 和视频点名称必须与埋点表完全一致,区分大小写。
- 标准顺序是:启动玩法 → 进入模式 → 开始关卡 → 过程事件 → 结果事件。
- 同一局只上报一个最终结果;中途挂起不能同时写成失败。
- SDK 自动生成的时长、广告和支付事件,业务层不要重复上报。
- 自定义属性只传埋点表已定义的字段,不要覆盖 SDK 维护的公共字段。
- 不得在埋点中写入手机号、身份证、Token 等敏感信息。
2. 按引擎接入
2.1 Unity 接入入口
csharp
using ZMYSDK.Report;
using ZMYSDK.Report.JsonProperties;统一入口为 ReportManager.I。事件数据类禁止直接 new,必须通过 GetNewReportJsonData<T>() 获取。
Unity 的 game_name 不在埋点调用处传入:ZMYAPPSetUp 会在初始化时读取 ZMYSDKAPPGlobalConfig.json 的 gameName,并传给 ZMYSDKManager.I.InitSDK(...)。之后 GetNewReportJsonData<T>() 会自动把它写入事件对象。
2.2 Unity 最小闭环
csharp
public class StatisticsExample
{
private int currentLevel = 50;
public void EnterGame()
{
// game_name 已在 SDK 初始化时注入;这里只启动玩法事件
ReportManager.I.StartMineGame();
// 设置 mode_name = normal
ReportManager.I.StartGameModel("normal");
}
public void StartLevel()
{
game_start data = ReportManager.I.GetNewReportJsonData<game_start>();
// 每一局开始时设置 mode_level
data.mode_level = currentLevel;
ReportManager.I.NewReportGameStart(data);
}
public void CompleteLevel(int score)
{
game_complete data = ReportManager.I.GetNewReportJsonData<game_complete>();
data.mode_level = currentLevel;
data.mode_level_score = score;
ReportManager.I.NewReportGameComplete(data);
}
public void BackToHome()
{
ReportManager.I.StartGameModel("None");
}
}game_start.mode_level 必填
NewReportGameStart 的实现会读取 mode_level.Value。未赋值会在运行时抛出异常,不能依赖默认值。
2.3 Unity 玩法与模式
| API | 调用时机 | SDK 行为 |
|---|---|---|
StartMineGame() | SDK 初始化完成、正式进入游戏 | 产生 start_first、start,启动 gaming 时长上报 |
StartGameModel(name) | 进入或切换模式 | 产生 mode_start,启动 mode_gaming 时长上报 |
StartGameModel("None") | 退出当前模式 | 停止上一模式计时 |
StartMineGame() 每次 SDK 生命周期只调用一次。重复调用会重复产生 start 并重新创建玩法计时器。
2.4 Unity 关卡状态机
text
game_start
├─ game_pause ── game_continue ──┐
├─ game_quit ── game_continue ──┤
├─ game_fail ── game_revive ───┤
├─ game_progress / game_foul │
└─ game_complete / game_draw / game_skip| 事件 | 数据类型 | 上报方法 | 说明 |
|---|---|---|---|
game_start | game_start | NewReportGameStart | 开始或重新开始一局 |
game_progress | game_progress | NewReportGameProgress | 进度或分数变化 |
game_foul | game_foul | NewReportGameFoul | 犯规或负反馈 |
game_pause | game_pause | NewReportGamePause | 用户主动暂停 |
game_continue | game_continue | NewReportGameContinue | 从暂停或挂起恢复 |
game_quit | game_quit | NewReportGameQuit | 中途离开并挂起 |
game_fail | game_fail | NewReportGameFail | 失败,之后可复活 |
game_revive | game_revive | NewReportGameRevive | 仅在失败后调用 |
game_complete | game_complete | NewReportGameComplete | 成功结算 |
game_draw | game_draw | NewReportGameDraw | 平局结算 |
game_skip | game_skip | NewReportGameSkip | 跳过并结束关卡 |
过程和结果事件统一使用以下写法。mode_name 与 _duration 由 SDK 写入;为兼容不同 SDK 版本,业务层在每条关卡事件上显式填写 mode_level。
csharp
game_progress progress = ReportManager.I.GetNewReportJsonData<game_progress>();
progress.mode_level = 50;
progress.mode_level_progress = 80;
progress.mode_level_score = 900;
ReportManager.I.NewReportGameProgress(progress);
game_fail fail = ReportManager.I.GetNewReportJsonData<game_fail>();
fail.mode_level = 50;
fail.mode_level_score = 900;
ReportManager.I.NewReportGameFail(fail);状态约束:
game_continue只能跟在game_pause或game_quit后。game_revive只能跟在game_fail后。game_quit表示挂起,不是最终失败。game_complete、game_draw和game_skip会结束当前关卡。
2.5 Unity 预定义业务事件
道具、活动、养成、UI、资源和登录事件均使用“数据类 + 同名方法”:
csharp
item_get data = ReportManager.I.GetNewReportJsonData<item_get>();
data.item_id = 1001;
data.item_type1 = 0; // 类型值以埋点表为准
data.item_number = 100; // 本次变化量
data.item_count = 200; // 变化后余额
data.item_game = "KXPYP";
ReportManager.I.NewReport_item_get(data);命名规则:
text
数据类型:<event_name>
上报方法:ReportManager.I.NewReport_<event_name>(data)
最终事件:<event_name>常用分类包括:
| 分类 | 常用事件 |
|---|---|
| 物品 | item_get, item_cost, item_use |
| 道具 | tools_unlock, tools_fail, tools_use |
| 活动 | activity_enter, activity_start, activity_success, activity_fail, activity_reward, activity_complete |
| 养成 | develop_trail, develop_unlock, develop_use, develop_levelup |
| UI | element_show, element_use, element_result, popup_show, popup_redirect |
| 资源 | res_downloadstart, res_downloadover, res_downloadfail, res_read_fail |
完整事件目录和字段以工程中的 ReportDefineByTools.cs、NewReportManagerByTools.cs 为准,也可查看 Unity 埋点详细参考。
SDK 维护侧:从线上同步预定义事件
ReportDefineByTools.cs 和 NewReportManagerByTools.cs 是生成文件,不要直接修改。维护 Unity SDK 源码时,在 Unity 菜单中执行 Tool/导出上报脚本,工具会依次完成:
- 从统计后台获取事件、属性及事件属性关联。
- 按游戏事件和游戏属性规则生成
Assets/Editor/Report下的本地配置。 - 重新生成
Assets/ZMYSDK/Runtime/Report/ReportDefineByTools.cs和NewReportManagerByTools.cs。
线上 number 属性生成可空 int 字段,array_map 属性生成 object 字段并按 JSON 对象或数组序列化。同步后必须编译 SDK,并核对新增事件、字段类型和关联关系。该流程仅用于 SDK 源码维护,普通业务接入项目不需要执行。
2.6 Unity 激励视频
使用 SDK VideoButton 组件时,以下事件自动上报:
video_unable、video_noAds、video_show、video_click、video_play、video_success、video_fail。
业务层必须正确配置 video_name。原有 video_param1、video_param2 继续兼容;需要携带更多统计字段时,可以给 VideoButton.VideoExtraProperties 赋值:
csharp
using System.Collections.Generic;
videoButton.VideoExtraProperties = new Dictionary<string, object>
{
{ "video_scene", "level_fail" },
{ "reward_count", 3 },
{ "is_first", true },
};该属性会自动附加到 video_unable、video_noAds、video_show、video_click、video_play、video_success 和 video_fail。不要在广告回调中再次调用 NewReportVideo*,否则会产生重复事件。
自定义广告入口且没有使用 VideoButton 时,才按真实生命周期调用:
csharp
using System.Collections.Generic;
var extra = new Dictionary<string, object>
{
{ "video_scene", "level_fail" },
{ "reward_count", 3 },
{ "is_first", true },
};
ReportManager.I.NewReportVideoShow("double_reward", extra);
ReportManager.I.NewReportVideoClick("double_reward", extra);
ReportManager.I.NewReportVideoPlay("double_reward", extra);
ReportManager.I.NewReportVideoSuccess("double_reward", extra);video_show、video_click、video_play、video_success 和 video_fail 同时保留原有参数与动态属性的混合重载:
csharp
// 旧写法:继续支持 video_param1 / video_param2
ReportManager.I.NewReportVideoClick("double_reward", "param1", "param2");
// 混合写法:保留 param1 / param2,同时增加动态属性
ReportManager.I.NewReportVideoClick(
"double_reward",
"param1",
"param2",
extra
);video_unable 和 video_noAds 原本没有 video_param1/2 参数,分别支持“只传 video_name”和“video_name + extra”两种形式。
动态属性值支持字符串、数值、布尔值、集合以及 LitJson 可以序列化的对象。属性名必须已在统计后台配置;空属性名会被忽略。同名动态属性会覆盖 SDK 自动生成的基础属性,因此不要在动态属性中传 game_name、mode_name、mode_level 或 video_name。如果动态属性序列化失败,SDK 会记录错误日志,并回退为只上报基础视频属性。
2.7 Unity 自定义事件
存在预定义数据类时优先使用 OnNewEvent(data):
csharp
item_get data = ReportManager.I.GetNewReportJsonData<item_get>();
data.item_id = 1001;
ReportManager.I.OnNewEvent(data);只有后台定义了 SDK 未覆盖的事件时,才使用底层接口:
csharp
using System.Collections.Generic;
using LitJson;
using ZMYSDK;
var properties = new Dictionary<string, object>
{
{ "page_name", "shop" },
{ "button_name", "buy" },
};
ZMYSDKManager.I.Sdk.OnNewEvent(
"custom_button_click",
JsonMapper.ToJson(properties)
);管理器属性名是 Sdk,不是旧文档中的 SDK。
2.8 Unity 设置用户属性
用户属性需要先在统计后台配置。调用时将属性序列化为 JSON 对象字符串,并通过 ZMYSDKManager.I.Sdk 设置:
csharp
using System.Collections.Generic;
using LitJson;
using ZMYSDK;
var userProperties = new Dictionary<string, object>
{
{ "name", "petter" },
{ "age", 18 },
};
ZMYSDKManager.I.Sdk.setUserProperty(
JsonMapper.ToJson(userProperties),
true
);接口定义:
csharp
void setUserProperty(string jsonString, bool isSaveToLocal);| 参数 | 说明 |
|---|---|
jsonString | 用户属性的 JSON 对象字符串,例如 {"name":"petter","age":18};属性名和值应符合统计后台配置 |
isSaveToLocal | 是否将属性保存到本地;设为 true 后,后续信息上报会一并上传已保存的属性 |
请在 SDK 初始化完成后调用。不要上传手机号、身份证号、Token 等敏感信息,也不要传入统计后台未配置的属性。
2.1 Cocos Creator / Laya 接入入口
统一入口为:
javascript
const Stats = GameHelper.GameStatistics;以下接口由 App SDK 统一实现,Cocos Creator 与 Laya 业务侧使用同一套调用方式。
2.2 Cocos Creator / Laya 最小闭环
javascript
const Stats = GameHelper.GameStatistics;
// 首页与玩法:EnterGame 设置 game_name
Stats.EnterHome();
Stats.EnterGame("KXPYP");
// EnterMode 设置 mode_name
Stats.EnterMode("normal", {
mode_difficulty: 1,
});
// 每一局开始时,StartGame 设置 mode_level
Stats.StartGame(50);
// 可选过程事件
Stats.ProgressGame({
mode_level_progress: 80,
mode_level_score: 900,
});
// 成功、失败、平局三选一
Stats.SuccessGame(999);
// 离开模式并回首页
Stats.ExitMode();
Stats.EnterHome();中途主动退出时调用:
javascript
Stats.QuitGame(600, { quit_reason: "back_to_level_select" });2.3 Cocos Creator / Laya 主流程
| API | 调用时机 | 最终事件或行为 |
|---|---|---|
EnterHome() | 首次进入、从玩法回首页 | 停止上一玩法计时 |
EnterGame(gameName) | 进入具体玩法 | start / gaming 链路 |
EnterMode(modeName, modelInfo?) | 进入模式 | mode_start / mode_gaming 链路 |
ExitMode() | 离开模式 | 结束模式计时并清理关卡属性 |
StartGame(level, modeName?, extra?) | 开始或重开一局 | game_start |
ProgressGame(param) | 进度或分数变化 | game_progress |
PauseGame(score?, state?, extra?) | 用户主动暂停 | game_pause |
ContinueGame(level, modeName?, state?, extra?) | 从匹配的暂停原因恢复 | game_continue |
QuitGame(score?, extra?) | 中途退出 | game_quit |
AliveGame(score?, extra?) | 失败后复活 | game_revive |
SkipGame(score?, extra?) | 跳过关卡 | game_skip |
SuccessGame(score?, extra?) | 成功结算 | game_complete |
FailGame(score?, extra?) | 失败结算 | game_fail |
DrawGame(score?, extra?) | 平局结算 | game_draw |
FoulGame(score?, extra?) | 犯规或负反馈 | game_foul |
暂停与继续的 state 必须一致。默认是 GameHelper.PAUSE_STATE.POPUP;其他原因包括 ADS、BACKGROUND、PROGRESS。
javascript
Stats.PauseGame(600, GameHelper.PAUSE_STATE.POPUP, {
popup_name: "pause_panel",
});
Stats.ContinueGame(
50,
"normal",
GameHelper.PAUSE_STATE.POPUP
);2.4 Cocos Creator / Laya 道具
javascript
const ItemType = GameHelper.EItemType1;
// 获得 100 金币,变化后余额 200
Stats.GetItem(
1001,
ItemType.EItemType1_CURRENCY,
100,
200,
"KXPYP",
-1,
-1,
{ source: "reward_video" }
);
// 消耗 20 金币,变化后余额 180
Stats.CostItem(
1001,
ItemType.EItemType1_CURRENCY,
20,
180,
"KXPYP",
-1,
-1
);
// 使用不发生库存扣减的永久皮肤
Stats.UseItem(
3001,
ItemType.EItemType1_SKIN,
"KXPYP",
-1,
-1
);| 参数 | 规则 |
|---|---|
iItemId | 稳定且唯一的物品 ID,建议由项目枚举维护 |
iCount | 本次变化量;获得和消耗都传非负数 |
iRemainCount | 变化后的剩余数量 |
sItemGame | 物品所属玩法 |
iItemType2/3 | 可选分类,不用时传 -1 或省略 |
GetItem / CostItem 用于有数量变化的物品,UseItem 用于永久物品。不要把同一次变化重复上报为消耗和使用。
2.5 Cocos Creator / Laya 引导、活动与养成
javascript
// 引导:必须在模式和关卡已开始后调用
Stats.GuideStart({ guide_version: "v2" });
Stats.GuideStep(10, { step_name: "move" });
Stats.GuideOver({ guide_version: "v2" });
// 活动
Stats.HandleActivity(
GameHelper.EActivityType.START,
"spring_2026",
"daily_challenge",
0,
1,
"normal",
false,
{ entrance: "home_banner" }
);
// 养成
Stats.HandleDevelop(
GameHelper.EDevelopType.LEVELUP,
"hero",
12,
"gold:1000",
"warrior",
"fire"
);| 能力 | 枚举 |
|---|---|
| 活动 | WARMUP, UNLOCK, ENTER, START, SUCCESS, FAIL, REWARD, RESET, GIVEUP, COMPLETE |
| 养成 | TRAIL, UNLOCK, LEVELUP, USE |
2.6 Cocos Creator / Laya 广告与支付
使用 App SDK 的广告模块时,视频点击、开始播放、播放成功/失败由 AdsVideoManager 自动上报。业务层不要再调用 ClickVideo、StartPlayVideo、PlayedVideoOver、PlayedVideoFail、trackVideo。
ShowVideo 的 App 行为
当前 App SDK 中 ShowVideo 是兼容空实现。不要为了补视频曝光而重复造事件;按 SDK 广告模块和埋点方案的约定验收。
Cocos App 上报 mode_level_index
mode_level_index 为业务传入的关卡索引扩展属性,取值口径以埋点表为准。App 在每次调用 GameHelper.AdsVideoManager.showVideo(videoName, opts) 时,通过 opts.statExt 直接传入:
typescript
// videoName 对应的视频节点应已通过 addVideoNode 完成绑定。
GameHelper.AdsVideoManager.showVideo("revive", {
statExt: {
mode_level_index: 50,
},
});该属性附加到本次调用实际产生的 video_click、video_play、video_success 或 video_fail。每次播放均应传入当前关卡索引,不依赖小游戏中由 EnterMode 等接口建立的自动继承逻辑。
statExt 用于统计扩展属性;param 用于业务回调透传,不能替代 statExt。mode_level_index 不替代 mode_level,也不应覆盖 game_name、mode_name 或 video_name 等基础字段。属性需在统计后台完成配置,现有成功、失败和错误回调继续按原业务逻辑处理。
支付事件的当前边界:
| API | App 行为 |
|---|---|
ShowPay(payName, extra?) | 商品入口曝光,可按埋点表调用 |
ClickPay(payName, extra?) | iOS 业务补充;Android 由原生侧处理 |
PaySuccess(...) | 当前兼容空实现,成功事件由平台侧处理 |
PayFail(payName, extra?) | iOS 业务补充;Android 由原生侧处理 |
ClickRestoreBuy(extra?) | 发起恢复购买 |
RestoreSuccess(payName, extra?) | 恢复购买成功 |
除非接入任务明确要求,不要在支付回调里重复上报。
2.7 Cocos Creator / Laya UI 与自定义事件
App SDK 的 UI 埋点受在线配置控制,配置未开启时调用会被 SDK 忽略。
javascript
Stats.OpenPage("shop", { source: "home" });
Stats.UsePageElement("buy_button", GameHelper.EUIType.EUIType_BUTTON, {
product_id: "coin_1",
});
Stats.ClosePage("shop");标准接口无法覆盖时才使用 reportEvent:
javascript
Stats.reportEvent("custom_button_click", {
page_name: "shop",
button_name: "buy",
product_id: "coin_1",
});复杂属性值需要先转成 JSON 字符串:
javascript
Stats.reportEvent("wx_feed_status_change", {
action: JSON.stringify([{ test: 1 }, "b"]),
});完整接口参考可查看 Cocos 埋点详细参考。
2.8 Cocos Creator / Laya 设置用户属性
用户属性需要先在统计后台配置。接口接收 JSON 字符串,因此对象需要先通过 JSON.stringify 序列化:
javascript
const Stats = GameHelper.GameStatistics;
const userProperties = {
name: "petter",
age: 18,
};
Stats.setUserProperty(
JSON.stringify(userProperties),
true
);接口定义:
typescript
setUserProperty(jsonString: string, isSaveToLocal: boolean): void;| 参数 | 说明 |
|---|---|
jsonString | 用户属性的 JSON 对象字符串,例如 {"name":"petter","age":18};属性名和值应符合统计后台配置 |
isSaveToLocal | 是否将属性保存到本地;设为 true 后,后续信息上报会一并上传已保存的属性 |
请在 SDK 初始化完成后调用。不要直接传 JavaScript 对象,也不要上传手机号、身份证号、Token 等敏感信息或统计后台未配置的属性。
3. 自动与手动上报边界
| 能力 | SDK 自动处理 | 业务手动处理 |
|---|---|---|
| 启动与公共环境信息 | 是 | 否 |
| 玩法/模式/关卡时长 | 调用入口后自动定时 | 只负责正确开始/结束状态 |
| 关卡结果 | 否 | 成功、失败、平局、跳关按真实结果调用 |
| 激励视频 | 使用 SDK 组件/模块时自动 | 仅自定义广告入口按约定补充 |
| 支付 | 平台与 SDK 内部为主 | 仅文档明确的入口曝光、iOS 或恢复购买场景 |
| 道具、活动、养成 | 否 | 按埋点表调用 |
| 用户属性 | 调用接口后由 SDK 处理 | 按统计后台配置传入 JSON 对象字符串 |
| 自定义事件 | 否 | 仅标准接口未覆盖时调用 |
4. 联调与验收
4.1 推荐验收路径
至少完整跑通以下用例,并在控制台与埋点后台同时核对:
- 冷启动后进入玩法。
- 进入模式并开始一关。
- 暂停后继续。
- 失败后复活,或直接失败后开始下一局。
- 重新开始一关并成功结算。
- 中途退出一关;Unity 项目再恢复该关。
- 获取和消耗一次道具。
- 使用 SDK 广告组件完整播放一次激励视频。
- 设置一次用户属性,并确认后台收到的属性名、属性值与配置一致。
重点核对 event key、game_name、mode_name、mode_level、分数、数量以及自定义属性是否与埋点表一致。
4.2 日志判断
Unity 强类型事件会打印类似:
text
上报:OnNewEvent:key[game_start]--JsonProperties[{...}]Cocos / Laya 可在 SDK 调试日志中核对事件名和属性。日志出现只代表客户端已发起上报,最终仍需在埋点后台确认事件入库。
4.3 常见问题
| 现象 | 排查项 |
|---|---|
| 完全没有事件 | SDK 是否初始化完成;事件是否已在后台配置;网络是否可用 |
Unity game_start 空值异常 | 是否为 game_start.mode_level 赋值 |
| Unity 提示“关卡未开始” | 是否先调用 NewReportGameStart |
| Unity 继续/复活被拒绝 | 前一状态是否分别为暂停/挂起、失败 |
Cocos / Laya ContinueGame 不生效 | 暂停与继续的 PAUSE_STATE 是否一致 |
| 模式或关卡字段错误 | 是否按顺序先进入模式、再开始关卡 |
| 视频事件重复 | 是否使用 SDK 广告组件时又手动上报生命周期 |
| 属性串到下一条事件 | Unity 是否绕过 GetNewReportJsonData<T>();JS 是否复用并修改同一个属性对象 |
| 只有日志、后台没有 | 事件/属性配置、字段类型、网络和测试环境是否正确 |
4.4 提测清单
- [ ] 玩法、模式、关卡名称已与埋点表确认。
- [ ] 每局只有一个开始事件;重新开始会再次上报。
- [ ] 每局最终结果唯一,中途退出没有误写成失败。
- [ ] 没有手动重复上报 SDK 自动处理的时长、广告和支付事件。
- [ ] 道具变化量与变化后余额均正确。
- [ ] 自定义事件及属性已在后台配置,且不含敏感信息。
- [ ] Android 与 iOS 均完成至少一次真机验证。
点我快速对接



›
‹